Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mark memory and disk storage as public #1649

Merged
merged 2 commits into from
Mar 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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