From 4e65bad08f00f567eb3a94400f73b2edb4a3d7b3 Mon Sep 17 00:00:00 2001 From: onevcat Date: Sun, 7 Mar 2021 00:51:50 +0900 Subject: [PATCH 1/2] Public disk storage methods --- Sources/Cache/DiskStorage.swift | 70 +++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/Sources/Cache/DiskStorage.swift b/Sources/Cache/DiskStorage.swift index 25e4b8748..7fca53fb5 100644 --- a/Sources/Cache/DiskStorage.swift +++ b/Sources/Cache/DiskStorage.swift @@ -116,7 +116,14 @@ public enum DiskStorage { } } - func store( + /// Stores a value to the storage under the specified key and expiration policy. + /// - Parameters: + /// - value: The value to be stored. + /// - key: The key to which the `value` will be stored. If there is already a value under the key, + /// the old value will be overwritten by `value`. + /// - expiration: The expiration policy used by this store action. + /// - Throws: An error during converting the value to a data format or during writing it to disk. + public func store( value: T, forKey key: String, expiration: StorageExpiration? = nil) throws @@ -170,7 +177,13 @@ public enum DiskStorage { } } - func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) throws -> T? { + /// Gets a value from the storage. + /// - Parameters: + /// - key: The cache key of value. + /// - extendingExpiration: The expiration policy used by this getting action. + /// - Throws: An error during converting the data to a value or during operation of disk files. + /// - Returns: The value under `key` if it is valid and found in the storage. Otherwise, `nil`. + public func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) throws -> T? { return try value(forKey: key, referenceDate: Date(), actuallyLoad: true, extendingExpiration: extendingExpiration) } @@ -224,11 +237,28 @@ public enum DiskStorage { } } - func isCached(forKey key: String) -> Bool { + /// Whether there is valid cached data under a given key. + /// - Parameter key: The cache key of value. + /// - Returns: If there is valid data under the key, `true`. Otherwise, `false`. + /// + /// - Note: + /// This method does not actually load the data from disk, so it is faster than directly loading the cached value + /// by checking the nullability of `value(forKey:extendingExpiration:)` method. + /// + public func isCached(forKey key: String) -> Bool { return isCached(forKey: key, referenceDate: Date()) } - func isCached(forKey key: String, referenceDate: Date) -> Bool { + /// Whether there is valid cached data under a given key and a reference date. + /// - Parameters: + /// - key: The cache key of value. + /// - referenceDate: A reference date to check whether the cache is still valid. + /// - Returns: If there is valid data under the key, `true`. Otherwise, `false`. + /// + /// - Note: + /// If you pass `Date()` to `referenceDate`, this method is identical to `isCached(forKey:)`. Use the + /// `referenceDate` to determine whether the cache is still valid for a future date. + public func isCached(forKey key: String, referenceDate: Date) -> Bool { do { let result = try value( forKey: key, @@ -242,7 +272,10 @@ public enum DiskStorage { } } - func remove(forKey key: String) throws { + /// Removes a value from a specified key. + /// - Parameter key: The cache key of value. + /// - Throws: An error during removing the value. + public func remove(forKey key: String) throws { let fileURL = cacheFileURL(forKey: key) try removeFile(at: fileURL) } @@ -251,7 +284,9 @@ public enum DiskStorage { try config.fileManager.removeItem(at: url) } - func removeAll() throws { + /// Removes all values in this storage. + /// - Throws: An error during removing the values. + public func removeAll() throws { try removeAll(skipCreatingDirectory: false) } @@ -264,12 +299,13 @@ public enum DiskStorage { /// The URL of the cached file with a given computed `key`. /// + /// - Parameter key: The final computed key used when caching the image. Please note that usually this is not + /// the `cacheKey` of an image `Source`. It is the computed key with processor identifier considered. + /// /// - Note: /// This method does not guarantee there is an image already cached in the returned URL. It just gives your /// the URL that the image should be if it exists in disk storage, with the give key. /// - /// - Parameter key: The final computed key used when caching the image. Please note that usually this is not - /// the `cacheKey` of an image `Source`. It is the computed key with processor identifier considered. public func cacheFileURL(forKey key: String) -> URL { let fileName = cacheFileName(forKey: key) return directoryURL.appendingPathComponent(fileName, isDirectory: false) @@ -305,7 +341,14 @@ public enum DiskStorage { return urls } - func removeExpiredValues(referenceDate: Date = Date()) throws -> [URL] { + /// Removes all expired values from this storage. + /// - Throws: A file manager error during removing the file. + /// - Returns: The URLs for removed files. + public func removeExpiredValues() throws -> [URL] { + return try removeExpiredValues(referenceDate: Date()) + } + + func removeExpiredValues(referenceDate: Date) throws -> [URL] { let propertyKeys: [URLResourceKey] = [ .isDirectoryKey, .contentModificationDateKey @@ -330,6 +373,11 @@ public enum DiskStorage { return expiredFiles } + /// Removes all size exceeded values from this storage. + /// - Throws: A file manager error during removing the file. + /// - Returns: The URLs for removed files. + /// + /// - Note: This method checks `config.sizeLimit` and remove cached files in an LRU (Least Recently Used) way. func removeSizeExceededValues() throws -> [URL] { if config.sizeLimit == 0 { return [] } // Back compatible. 0 means no limit. @@ -364,8 +412,8 @@ public enum DiskStorage { return removed } - /// Get the total file size of the folder in bytes. - func totalSize() throws -> UInt { + /// Gets the total file size of the folder in bytes. + public func totalSize() throws -> UInt { let propertyKeys: [URLResourceKey] = [.fileSizeKey] let urls = try allFileURLs(for: propertyKeys) let keys = Set(propertyKeys) From 044cc0661186fba13b369f3891ea199241ca1774 Mon Sep 17 00:00:00 2001 From: onevcat Date: Sun, 7 Mar 2021 00:54:58 +0900 Subject: [PATCH 2/2] Public memory storage --- Sources/Cache/ImageCache.swift | 4 +- Sources/Cache/MemoryStorage.swift | 41 +++++++++++-------- Tests/KingfisherTests/ImageCacheTests.swift | 4 +- .../ImagePrefetcherTests.swift | 4 +- .../KingfisherTests/MemoryStorageTests.swift | 32 +++++++-------- 5 files changed, 46 insertions(+), 39 deletions(-) diff --git a/Sources/Cache/ImageCache.swift b/Sources/Cache/ImageCache.swift index 68be81885..bbb86428a 100644 --- a/Sources/Cache/ImageCache.swift +++ b/Sources/Cache/ImageCache.swift @@ -465,7 +465,7 @@ open class ImageCache { let computedKey = key.computedKey(with: identifier) if fromMemory { - try? memoryStorage.remove(forKey: computedKey) + memoryStorage.remove(forKey: computedKey) } if fromDisk { @@ -633,7 +633,7 @@ open class ImageCache { /// Clears the memory storage of this cache. @objc public func clearMemoryCache() { - try? memoryStorage.removeAll() + memoryStorage.removeAll() } /// Clears the disk storage of this cache. This is an async operation. diff --git a/Sources/Cache/MemoryStorage.swift b/Sources/Cache/MemoryStorage.swift index b4abc712b..f74220b75 100644 --- a/Sources/Cache/MemoryStorage.swift +++ b/Sources/Cache/MemoryStorage.swift @@ -79,7 +79,8 @@ public enum MemoryStorage { } } - func removeExpired() { + /// Removes the expired values from the storage. + public func removeExpired() { lock.lock() defer { lock.unlock() } for key in keys { @@ -98,12 +99,16 @@ public enum MemoryStorage { } } - // Storing in memory will not throw. It is just for meeting protocol requirement and - // forwarding to no throwing method. - func store( + /// Stores a value to the storage under the specified key and expiration policy. + /// - Parameters: + /// - value: The value to be stored. + /// - key: The key to which the `value` will be stored. + /// - expiration: The expiration policy used by this store action. + /// - Throws: No error will + public func store( value: T, forKey key: String, - expiration: StorageExpiration? = nil) throws + expiration: StorageExpiration? = nil) { storeNoThrow(value: value, forKey: key, expiration: expiration) } @@ -126,17 +131,13 @@ public enum MemoryStorage { keys.insert(key) } - /// Use this when you actually access the memory cached item. - /// By default, this will extend the expired data for the accessed item. + /// Gets a value from the storage. /// /// - Parameters: - /// - key: Cache Key - /// - extendingExpiration: expiration value to extend item expiration time: - /// * .none: The item expires after the original time, without extending after access. - /// * .cacheTime: The item expiration extends by the original cache time after each access. - /// * .expirationTime: The item expiration extends by the provided time after each access. - /// - Returns: cached object or nil - func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) -> T? { + /// - key: The cache key of value. + /// - extendingExpiration: The expiration policy used by this getting action. + /// - Returns: The value under `key` if it is valid and found in the storage. Otherwise, `nil`. + public func value(forKey key: String, extendingExpiration: ExpirationExtending = .cacheTime) -> T? { guard let object = storage.object(forKey: key as NSString) else { return nil } @@ -147,21 +148,27 @@ public enum MemoryStorage { return object.value } - func isCached(forKey key: String) -> Bool { + /// Whether there is valid cached data under a given key. + /// - Parameter key: The cache key of value. + /// - Returns: If there is valid data under the key, `true`. Otherwise, `false`. + public func isCached(forKey key: String) -> Bool { guard let _ = value(forKey: key, extendingExpiration: .none) else { return false } return true } - func remove(forKey key: String) throws { + /// Removes a value from a specified key. + /// - Parameter key: The cache key of value. + public func remove(forKey key: String) { lock.lock() defer { lock.unlock() } storage.removeObject(forKey: key as NSString) keys.remove(key) } - func removeAll() throws { + /// Removes all values in this storage. + public func removeAll() { lock.lock() defer { lock.unlock() } storage.removeAllObjects() diff --git a/Tests/KingfisherTests/ImageCacheTests.swift b/Tests/KingfisherTests/ImageCacheTests.swift index 0695d960e..ef8005760 100644 --- a/Tests/KingfisherTests/ImageCacheTests.swift +++ b/Tests/KingfisherTests/ImageCacheTests.swift @@ -345,7 +345,7 @@ class ImageCacheTests: XCTestCase { var cacheType = self.cache.imageCachedType(forKey: key) XCTAssertEqual(cacheType, .memory) - try! self.cache.memoryStorage.remove(forKey: key) + self.cache.memoryStorage.remove(forKey: key) cacheType = self.cache.imageCachedType(forKey: key) XCTAssertEqual(cacheType, .disk) @@ -368,7 +368,7 @@ class ImageCacheTests: XCTestCase { var cacheType = self.cache.imageCachedType(forKey: key) XCTAssertEqual(cacheType, .memory) - try! self.cache.memoryStorage.remove(forKey: key) + self.cache.memoryStorage.remove(forKey: key) cacheType = self.cache.imageCachedType(forKey: key) XCTAssertEqual(cacheType, .disk) diff --git a/Tests/KingfisherTests/ImagePrefetcherTests.swift b/Tests/KingfisherTests/ImagePrefetcherTests.swift index 0fe85f225..c4dc6a57e 100644 --- a/Tests/KingfisherTests/ImagePrefetcherTests.swift +++ b/Tests/KingfisherTests/ImagePrefetcherTests.swift @@ -221,7 +221,7 @@ class ImagePrefetcherTests: XCTestCase { let key = testKeys[0] cache.store(KFCrossPlatformImage(), forKey: key) cache.store(testImage, forKey: key) { result in - try! cache.memoryStorage.remove(forKey: key) + cache.memoryStorage.remove(forKey: key) XCTAssertEqual(cache.imageCachedType(forKey: key), .disk) @@ -253,7 +253,7 @@ class ImagePrefetcherTests: XCTestCase { let key = testKeys[0] cache.store(testImage, forKey: key) { result in - try! cache.memoryStorage.remove(forKey: key) + cache.memoryStorage.remove(forKey: key) XCTAssertEqual(cache.imageCachedType(forKey: key), .disk) diff --git a/Tests/KingfisherTests/MemoryStorageTests.swift b/Tests/KingfisherTests/MemoryStorageTests.swift index 26473a742..cafa113c0 100644 --- a/Tests/KingfisherTests/MemoryStorageTests.swift +++ b/Tests/KingfisherTests/MemoryStorageTests.swift @@ -63,36 +63,36 @@ class MemoryStorageTests: XCTestCase { func testStoreAndGetValue() { XCTAssertFalse(storage.isCached(forKey: "1")) - try! storage.store(value: 1, forKey: "1") + storage.store(value: 1, forKey: "1") XCTAssertTrue(storage.isCached(forKey: "1")) XCTAssertEqual(storage.value(forKey: "1"), 1) } func testStoreValueOverwritting() { - try! storage.store(value: 1, forKey: "1") + storage.store(value: 1, forKey: "1") XCTAssertEqual(storage.value(forKey: "1"), 1) - try! storage.store(value: 100, forKey: "1") + storage.store(value: 100, forKey: "1") XCTAssertEqual(storage.value(forKey: "1"), 100) } func testRemoveValue() { XCTAssertFalse(storage.isCached(forKey: "1")) - try! storage.store(value: 1, forKey: "1") + storage.store(value: 1, forKey: "1") XCTAssertTrue(storage.isCached(forKey: "1")) - try! storage.remove(forKey: "1") + storage.remove(forKey: "1") XCTAssertFalse(storage.isCached(forKey: "1")) } func testRemoveAllValues() { - try! storage.store(value: 1, forKey: "1") - try! storage.store(value: 2, forKey: "2") + storage.store(value: 1, forKey: "1") + storage.store(value: 2, forKey: "2") XCTAssertTrue(storage.isCached(forKey: "1")) XCTAssertTrue(storage.isCached(forKey: "2")) - try! storage.removeAll() + storage.removeAll() XCTAssertFalse(storage.isCached(forKey: "1")) XCTAssertFalse(storage.isCached(forKey: "2")) } @@ -101,11 +101,11 @@ class MemoryStorageTests: XCTestCase { let exp = expectation(description: #function) XCTAssertFalse(storage.isCached(forKey: "1")) - try! storage.store(value: 1, forKey: "1", expiration: .seconds(0.1)) + storage.store(value: 1, forKey: "1", expiration: .seconds(0.1)) XCTAssertTrue(storage.isCached(forKey: "1")) XCTAssertFalse(storage.isCached(forKey: "2")) - try! storage.store(value: 2, forKey: "2") + storage.store(value: 2, forKey: "2") XCTAssertTrue(storage.isCached(forKey: "2")) delay(0.2) { @@ -126,7 +126,7 @@ class MemoryStorageTests: XCTestCase { storage.config.expiration = .seconds(0.1) XCTAssertFalse(storage.isCached(forKey: "1")) - try! storage.store(value: 1, forKey: "1") + storage.store(value: 1, forKey: "1") XCTAssertTrue(storage.isCached(forKey: "1")) delay(0.2) { @@ -143,7 +143,7 @@ class MemoryStorageTests: XCTestCase { let exp = expectation(description: #function) XCTAssertFalse(storage.isCached(forKey: "1")) - try! storage.store(value: 1, forKey: "1", expiration: .seconds(1)) + storage.store(value: 1, forKey: "1", expiration: .seconds(1)) XCTAssertTrue(storage.isCached(forKey: "1")) delay(0.1) { @@ -169,7 +169,7 @@ class MemoryStorageTests: XCTestCase { let exp = expectation(description: #function) XCTAssertFalse(storage.isCached(forKey: "1")) - try! storage.store(value: 1, forKey: "1", expiration: .seconds(1)) + storage.store(value: 1, forKey: "1", expiration: .seconds(1)) XCTAssertTrue(storage.isCached(forKey: "1")) delay(0.1) { @@ -194,7 +194,7 @@ class MemoryStorageTests: XCTestCase { let exp = expectation(description: #function) XCTAssertFalse(storage.isCached(forKey: "1")) - try! storage.store(value: 1, forKey: "1", expiration: .seconds(0.1)) + storage.store(value: 1, forKey: "1", expiration: .seconds(0.1)) XCTAssertTrue(storage.isCached(forKey: "1")) delay(0.2) { @@ -215,7 +215,7 @@ class MemoryStorageTests: XCTestCase { let exp = expectation(description: #function) let expiration = StorageExpiration.seconds(0.5) - try! storage.store(value: 1, forKey: "1", expiration: expiration) + storage.store(value: 1, forKey: "1", expiration: expiration) delay(0.3) { // This should extend the expiration to (0.3 + 0.5) from initially created. @@ -240,7 +240,7 @@ class MemoryStorageTests: XCTestCase { let config = MemoryStorage.Config(totalCostLimit: 3, cleanInterval: 0.1) storage = MemoryStorage.Backend(config: config) - try! storage.store(value: 1, forKey: "1", expiration: .seconds(0.1)) + storage.store(value: 1, forKey: "1", expiration: .seconds(0.1)) XCTAssertTrue(storage.isCached(forKey: "1")) XCTAssertEqual(self.storage.keys.count, 1)