Skip to content

Commit

Permalink
Merge pull request #1649 from onevcat/feature/public-cache
Browse files Browse the repository at this point in the history
Mark memory and disk storage as `public`
  • Loading branch information
onevcat authored Mar 7, 2021
2 parents ba51e9d + 044cc06 commit 9a5cf4c
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 50 deletions.
70 changes: 59 additions & 11 deletions Sources/Cache/DiskStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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,
Expand All @@ -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)
}
Expand All @@ -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)
}

Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Cache/ImageCache.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
41 changes: 24 additions & 17 deletions Sources/Cache/MemoryStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand All @@ -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
}
Expand All @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions Tests/KingfisherTests/ImageCacheTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)

Expand Down
4 changes: 2 additions & 2 deletions Tests/KingfisherTests/ImagePrefetcherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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)

Expand Down
32 changes: 16 additions & 16 deletions Tests/KingfisherTests/MemoryStorageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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.
Expand All @@ -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)

Expand Down

0 comments on commit 9a5cf4c

Please sign in to comment.