structFileMeta{ let url: URL let lastAccessDate: Date? let estimatedExpirationDate: Date? let isDirectory: Bool let fileSize: Int init( fileURL: URL, lastAccessDate: Date?, estimatedExpirationDate: Date?, isDirectory: Bool, fileSize: Int) { self.url = fileURL self.lastAccessDate = lastAccessDate self.estimatedExpirationDate = estimatedExpirationDate self.isDirectory = isDirectory self.fileSize = fileSize } funcexpired(referenceDate: Date) -> Bool { return estimatedExpirationDate?.isPast(referenceDate: referenceDate) ?? true } // 根据当前缓存对象的续期策略,进行续期。 funcextendExpiration(with fileManager: FileManager, extendingExpiration: ExpirationExtending) { guardlet lastAccessDate = lastAccessDate, let lastEstimatedExpiration = estimatedExpirationDate else { return } let attributes: [FileAttributeKey : Any] switch extendingExpiration { case .none: // not extending expiration time here return case .cacheTime: let originalExpiration: StorageExpiration = .seconds(lastEstimatedExpiration.timeIntervalSince(lastAccessDate)) attributes = [ .creationDate: Date().fileAttributeDate, .modificationDate: originalExpiration.estimatedExpirationSinceNow.fileAttributeDate ] case .expirationTime(let expirationTime): attributes = [ .creationDate: Date().fileAttributeDate, .modificationDate: expirationTime.estimatedExpirationSinceNow.fileAttributeDate ] } try? fileManager.setAttributes(attributes, ofItemAtPath: url.path) } }
不难看出:FileMeta 的设计思路与 StorageObject 的设计思路有不少相似的地方。
缓存过期策略
Kingfisher 有设计 5 种缓存对象的过期策略:
1 2 3 4 5 6 7 8 9
publicenumStorageExpiration{ case never // 缓存对象永不过期 case seconds(TimeInterval) // 缓存对象在当前时间点后的 x 秒过期 case days(Int) // 缓存对象在当前时间点后的 x 天过期 case date(Date) // 缓存对象在指定 Date 过期 case expired // 缓存对象已过期 ... ... }
/// Represents the error reason during networking request phase. case requestError(reason: RequestErrorReason) /// Represents the error reason during networking response phase. case responseError(reason: ResponseErrorReason) /// Represents the error reason during Kingfisher caching system. case cacheError(reason: CacheErrorReason) /// Represents the error reason during image processing phase. case processorError(reason: ProcessorErrorReason) /// Represents the error reason during image setting in a view related class. case imageSettingError(reason: ImageSettingErrorReason)
classTask{ var onFinished: ((String) -> String?)? funcfinish(taskID: String) { let string = onFinished?(taskID) print(String(describing: string)) } }
classKFViewController: UIViewController{ let subtaskAmount = 3 overridefuncviewDidLoad() { super.viewDidLoad() let task = Task() task.onFinished = { [weakself] taskID in guardletself = selfelse { returnnil } let string = "Task \(taskID) is finished, the number of subtasks is \(self.subtaskAmount)" return string } // 输出:Optional("Task 123 is finished, the number of subtasks is 3") task.finish(taskID: "123")
classTask{ var onFinished = Delegate<String, String>() funcfinish(_ taskID: String) { let string = onFinished.call(taskID) print(String(describing: string)) } }
classKFViewController: UIViewController{ let subtaskAmount = 3 overridefuncviewDidLoad() { super.viewDidLoad() let task = Task() task.onFinished.delegate(on: self) { (self, taskID) in let string = "Task \(taskID) is finished, the amount of its subtask is \(self.subtaskAmount)" return string } task.finish("123") ... ...
下面第二个 self 已是弱引用后的版本,无需每次得加 weak 关键字了。
1
task.onFinished.delegate(on: self) { (self, taskID) in
再者,把匿名函数(Closure)包装成一个 Delegate 对象使用使得代码更加清晰。
提问 1:为啥 onFinished.call(taskID) 返回的是可选类型 String?
1 2
// 这里的范型约束明明是 String 类型 var onFinished = Delegate<String, String>()
enumState{ case idle case imageCached case originalImageCached case done }
enumAction{ case cacheInitiated case cachingImage case cachingOriginalImage }
funcapply(_ action: Action, trigger: () -> Void) { switch (state, action) { case (.done, _): break // From .idle case (.idle, .cacheInitiated): if !shouldWaitForCache { state = .done trigger() } case (.idle, .cachingImage): if shouldCacheOriginal { state = .imageCached } else { state = .done trigger() } case (.idle, .cachingOriginalImage): state = .originalImageCached // From .imageCached case (.imageCached, .cachingOriginalImage): state = .done trigger() // From .originalImageCached case (.originalImageCached, .cachingImage): state = .done trigger() default: assertionFailure("This case should not happen in CacheCallbackCoordinator: \(state) - \(action)") } }