使用Swift开发一个贝乐虎启蒙App - 视频、音频、读绘本使用记录

414 阅读3分钟

前言

前面我们已经把视频播放、音频播放、读绘本搞好了,现在我们就来搞下使用记录吧。

使用记录

记录视频播放、音频播放、读绘本的使用记录我们使用Cache这个库,这个缓存库挺强大的,推荐你们使用。

下面我们简单的来了解下Cache

支持以下存储

  • MemoryStorage: save object to memory.
  • DiskStorage: save object to disk.
  • HybridStorage: save object to memory and disk, so you get persistented object on disk, while fast access with in memory objects.
  • SyncStorage: blocking APIs, all read and write operations are scheduled in a serial queue, all sync manner.
  • AsyncStorage: non-blocking APIs, operations are scheduled in an internal queue for serial processing. No read and write should happen at the same time.

可以通过MemoryConfig来配置MemoryStorage

public struct MemoryConfig {
    /// Expiry date that will be applied by default for every added object
    /// if it's not overridden in the add(key: object: expiry: completion:) method
    public let expiry: Expiry

    /// The maximum number of objects in memory the cache should hold.
    /// If 0, there is no count limit. The default value is 0.
    public let countLimit: UInt

    /// The maximum total cost that the cache can hold before it starts evicting objects.
    /// If 0, there is no total cost limit. The default value is 0
    public let totalCostLimit: UInt
}

可以通过DiskConfig来配置DiskStorage

public struct DiskConfig {
    /// The name of disk storage, this will be used as folder name within directory
    public let name: String

    /// Expiry date that will be applied by default for every added object
    /// if it's not overridden in the add(key: object: expiry: completion:) method
    public let expiry: Expiry

    /// Maximum size of the disk cache storage (in bytes)
    public let maxSize: UInt

    /// A folder to store the disk cache contents. Defaults to a prefixed directory in Caches if nil
    public let directory: URL?

    #if os(iOS) || os(tvOS)
    /// Data protection is used to store files in an encrypted format on disk and to decrypt them on demand.
    /// Support only on iOS and tvOS.
    public let protectionType: FileProtectionType?

    public init(name: String, expiry: Expiry = .never,
              maxSize: UInt = 0, directory: URL? = nil,
              protectionType: FileProtectionType? = nil) {
    self.name = name
    self.expiry = expiry
    self.maxSize = maxSize
    self.directory = directory
    self.protectionType = protectionType
    }
    #else
    public init(name: String, expiry: Expiry = .never,
              maxSize: UInt = 0, directory: URL? = nil) {
    self.name = name
    self.expiry = expiry
    self.maxSize = maxSize
    self.directory = directory
    }
    #endif
}

Codable types

Storage supports any objects that conform to Codable protocol. You can make your own things conform to Codable so that can be saved and loaded from Storage.

The supported types are

  • Primitives like IntFloatStringBool, ...
  • Array of primitives like [Int][Float][Double], ...
  • Set of primitives like Set<String>Set<Int>, ...
  • Simply dictionary like [String: Int][String: String], ...
  • Date
  • URL
  • Data
// Save to storage
try? storage.setObject(10, forKey: "score")
try? storage.setObject("Oslo", forKey: "my favorite city", expiry: .never)
try? storage.setObject(["alert", "sounds", "badge"], forKey: "notifications")
try? storage.setObject(data, forKey: "a bunch of bytes")
try? storage.setObject(authorizeURL, forKey: "authorization URL")

// Load from storage
let score = try? storage.object(forKey: "score")
let favoriteCharacter = try? storage.object(forKey: "my favorite city")

// Check if an object exists
let hasFavoriteCharacter = try? storage.existsObject(forKey: "my favorite city")

// Remove an object in storage
try? storage.removeObject(forKey: "my favorite city")

// Remove all objects
try? storage.removeAll()

// Remove expired objects
try? storage.removeExpiredObjects()

还可以自定义,要遵守Codable

struct User: Codable {
  let firstName: String
  let lastName: String
}

let user = User(fistName: "John", lastName: "Snow")
try? storage.setObject(user, forKey: "character")

封装Cache

由于我们会把使用记录存到磁盘中去,而Cache库存的时候,会根据keymd5后作为文件名称,这样会有一个问题,就是我们获取记录的时候没法获取。所以我们要改下这个库,存的时候我们自己md5

extension DiskStorage {

    func makeFileName(for key: String) -> String {
        let fileExtension = URL(fileURLWithPath: key).pathExtension

        // let fileName = MD5(key)
        let fileName = key

        switch fileExtension.isEmpty {
        case true:
          return fileName
        case false:
          return "\(fileName).\(fileExtension)"
        }
    }
}

新建一个CacheManager管理类

class CacheManager<T: Codable>: NSObject {

    private var storage: Storage<T>?

    init(name: String = "Cache") {
        let diskConfig = DiskConfig(name: name, directory: URL(fileURLWithPath: NSHomeDirectory() + "/Documents/Cache/"))
        let memoryConfig = MemoryConfig(expiry: .never)
        do {
            storage = try Storage(diskConfig: diskConfig, memoryConfig: memoryConfig, transformer: TransformerFactory.forCodable(ofType: T.self))
        } catch {
            debugPrint("CacheManager init storage error: \(error)")
        }
    }
    
    /// 保存
    func setObject(_ object: T, forKey: String) {
        do {
            try storage?.setObject(object, forKey: forKey)
        } catch {
            debugPrint("CacheManager save obiect error: \(error)")
        }
    }
    
    /// 获取
    func object(forKey key: String) -> T? {
        do {
            return try storage?.object(forKey: key) ?? nil
        } catch {
            debugPrint("CacheManager get obiect error: \(error)")
            return nil
        }
    }
    
    /// 根据key移除
    func removeObiect(forKey key: String) {
        do {
            try storage?.removeObject(forKey: key)
        } catch {
            debugPrint("CacheManager remove obiect error: \(error)")
        }
    }
    
    /// 移除所有
    func removeAll() {
        do {
            try storage?.removeAll()
        } catch {
            debugPrint("CacheManager remove all obiect error: \(error)")
        }
    }
}

使用

以保存绘本为例

// MAKE: 绘本
class BookCacheManager {

    static let share = BookCacheManager()
    private let cacheManger = CacheManager<RecChildDataModel>(name: "Book")
    private let path = NSHomeDirectory() + "/Documents/Cache/Book"

    /// 保存绘本
    func setBook(_ model: RecChildDataModel, forKey: String) {
        cacheManger.setObject(model, forKey: forKey.MD5String)
    }

    /// 获取绘本
    func object(forKey key: String) -> RecChildDataModel? {
        cacheManger.object(forKey: key)
    }

    /// 移除绘本
    func removeObiect(forKey key: String) {
        cacheManger.removeObiect(forKey: key.MD5String)
    }

    /// 移除所有绘本
    func removeAll() {
        cacheManger.removeAll()
    }

    /// 获取所有绘本的缓存
    func getAllBooks() -> [RecChildDataModel] {
        do {
            let files = try FileManager.default.contentsOfDirectory(atPath: path)
            var books = [RecChildDataModel]()
            for file in files {
                if let book = object(forKey: file) {
                    books.append(book)
                }
            }
            return books
        } catch {
            debugPrint(error.localizedDescription)
            return []
        }
    }
}

在需要存的地方这样调用:

BookCacheManager.share.setBook(value, forKey: key)

在需要取的地方这样调用:

BookCacheManager.share.getAllBooks()