소스 검색

Add option to disable memory cache TTL extending

Dominique Stranz 6 년 전
부모
커밋
fddc74ed89

+ 1 - 5
Sources/Cache/ImageCache.swift

@@ -525,11 +525,7 @@ open class ImageCache {
         options: KingfisherParsedOptionsInfo) -> Image?
     {
         let computedKey = key.computedKey(with: options.processor.identifier)
-        do {
-            return try memoryStorage.value(forKey: computedKey)
-        } catch {
-            return nil
-        }
+        return memoryStorage.value(forKey: computedKey, extendingExpiration: options.memoryCacheReadExtendingExpiration)
     }
 
     /// Gets an image for a given key from the memory storage.

+ 6 - 0
Sources/General/KingfisherOptionsInfo.swift

@@ -203,6 +203,10 @@ public enum KingfisherOptionsInfoItem {
     /// value to overwrite the config setting for this caching item.
     case memoryCacheExpiration(StorageExpiration)
     
+    /// By default, every read operation for an item is extending it's expiration time. Use this setting to
+    /// disable this behavior and expire item regardless it's next requests.
+    case memoryCacheExpirationNotExtendable
+    
     /// The expiration setting for memory cache. By default, the underlying `DiskStorage.Backend` uses the
     /// expiration in its config for all items. If set, the `DiskStorage.Backend` will use this associated
     /// value to overwrite the config setting for this caching item.
@@ -252,6 +256,7 @@ public struct KingfisherParsedOptionsInfo {
     public var alsoPrefetchToMemory = false
     public var loadDiskFileSynchronously = false
     public var memoryCacheExpiration: StorageExpiration? = nil
+    public var memoryCacheReadExtendingExpiration = true
     public var diskCacheExpiration: StorageExpiration? = nil
     public var processingQueue: CallbackQueue? = nil
     public var progressiveJPEG: ImageProgressive? = nil
@@ -290,6 +295,7 @@ public struct KingfisherParsedOptionsInfo {
             case .loadDiskFileSynchronously: loadDiskFileSynchronously = true
             case .callbackDispatchQueue(let value): callbackQueue = value.map { .dispatch($0) } ?? .mainCurrentOrAsync
             case .memoryCacheExpiration(let expiration): memoryCacheExpiration = expiration
+            case .memoryCacheExpirationNotExtendable: memoryCacheReadExtendingExpiration = false
             case .diskCacheExpiration(let expiration): diskCacheExpiration = expiration
             case .processingQueue(let queue): processingQueue = queue
             case .progressiveJPEG(let value): progressiveJPEG = value

+ 79 - 1
Tests/KingfisherTests/ImageViewExtensionTests.swift

@@ -335,7 +335,7 @@ class ImageViewExtensionTests: XCTestCase {
         waitForExpectations(timeout: 3, handler: nil)
     }
     
-    func testCacnelImageTask() {
+    func testCancelImageTask() {
         let exp = expectation(description: #function)
         let url = testURLs[0]
         let stub = delayedStub(url, data: testImageData)
@@ -652,6 +652,84 @@ class ImageViewExtensionTests: XCTestCase {
         group.notify(queue: .main) { exp.fulfill() }
         waitForExpectations(timeout: 3, handler: nil)
     }
+    
+    func testCacheImageExtendingExpirationTask() {
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+        stub(url, data: testImageData)
+        
+        let options: KingfisherOptionsInfo = [.cacheMemoryOnly, .memoryCacheExpiration(.seconds(0.15))]
+        
+        let group = DispatchGroup()
+        
+        group.enter()
+        imageView.kf.setImage(with: url, options: options) { result in
+            XCTAssertNotNil(result.value?.image)
+            XCTAssertTrue(result.value!.cacheType == .none)
+            group.leave()
+        }
+        
+        group.enter()
+        delay(0.1) {
+            self.imageView.kf.setImage(with: url, options: options) { result in
+                XCTAssertNotNil(result.value?.image)
+                XCTAssertTrue(result.value!.cacheType == .memory)
+                group.leave()
+            }
+        }
+        
+        group.enter()
+        delay(0.2) {
+            self.imageView.kf.setImage(with: url, options: options) { result in
+                XCTAssertNotNil(result.value?.image)
+                XCTAssertTrue(result.value!.cacheType == .memory)
+                group.leave()
+            }
+        }
+        
+        group.notify(queue: .main, execute: exp.fulfill)
+        
+        waitForExpectations(timeout: 3, handler: nil)
+    }
+    
+    func testCacheImageExpirationTask() {
+        let exp = expectation(description: #function)
+        let url = testURLs[0]
+        stub(url, data: testImageData)
+        
+        let options: KingfisherOptionsInfo = [.cacheMemoryOnly, .memoryCacheExpirationNotExtendable, .memoryCacheExpiration(.seconds(0.15))]
+  
+        let group = DispatchGroup()
+        
+        group.enter()
+        imageView.kf.setImage(with: url, options: options) { result in
+            XCTAssertNotNil(result.value?.image)
+            XCTAssertTrue(result.value!.cacheType == .none)
+            group.leave()
+        }
+        
+        group.enter()
+        delay(0.1) {
+            self.imageView.kf.setImage(with: url, options: options) { result in
+                XCTAssertNotNil(result.value?.image)
+                XCTAssertTrue(result.value!.cacheType == .memory)
+                group.leave()
+            }
+        }
+        
+        group.enter()
+        delay(0.2) {
+            self.imageView.kf.setImage(with: url, options: options) { result in
+                XCTAssertNotNil(result.value?.image)
+                XCTAssertTrue(result.value!.cacheType == .none)
+                group.leave()
+            }
+        }
+        
+        group.notify(queue: .main, execute: exp.fulfill)
+        
+        waitForExpectations(timeout: 3, handler: nil)
+    }
 }
 
 extension View: Placeholder {}

+ 4 - 1
Tests/KingfisherTests/KingfisherOptionsInfoTests.swift

@@ -52,6 +52,7 @@ class KingfisherOptionsInfoTests: XCTestCase {
         XCTAssertFalse(options.keepCurrentImageWhileLoading)
         XCTAssertFalse(options.onlyLoadFirstFrame)
         XCTAssertFalse(options.cacheOriginalImage)
+        XCTAssertTrue(options.memoryCacheReadExtendingExpiration)
     }
     
     func testSetOptionsShouldParseCorrectly() {
@@ -88,7 +89,8 @@ class KingfisherOptionsInfoTests: XCTestCase {
             .imageModifier(modifier),
             .keepCurrentImageWhileLoading,
             .onlyLoadFirstFrame,
-            .cacheOriginalImage
+            .cacheOriginalImage,
+            .memoryCacheExpirationNotExtendable
         ])
         
         XCTAssertTrue(options.targetCache === cache)
@@ -109,6 +111,7 @@ class KingfisherOptionsInfoTests: XCTestCase {
         XCTAssertTrue(options.fromMemoryCacheOrRefresh)
         XCTAssertTrue(options.forceTransition)
         XCTAssertTrue(options.cacheMemoryOnly)
+        XCTAssertFalse(options.memoryCacheReadExtendingExpiration)
         XCTAssertTrue(options.waitForCache)
         XCTAssertTrue(options.onlyFromCache)
         XCTAssertTrue(options.backgroundDecode)

+ 50 - 0
Tests/KingfisherTests/MemoryStorageTests.swift

@@ -138,6 +138,56 @@ class MemoryStorageTests: XCTestCase {
         }
         waitForExpectations(timeout: 3, handler: nil)
     }
+    
+    func testStoreWithExpirationExtending() {
+        let exp = expectation(description: #function)
+        
+        XCTAssertFalse(storage.isCached(forKey: "1"))
+        try! storage.store(value: 1, forKey: "1", expiration: .seconds(0.15))
+        XCTAssertTrue(storage.isCached(forKey: "1"))
+        
+        XCTAssertFalse(storage.isCached(forKey: "2"))
+        try! storage.store(value: 2, forKey: "2")
+        XCTAssertTrue(storage.isCached(forKey: "2"))
+        
+        delay(0.1) {
+            // Request for the object to extend it's expiration date
+            let obj = self.storage.value(forKey: "1", extendingExpiration: true)
+            XCTAssertNotNil(obj)
+        }
+        
+        delay(0.2) {
+            XCTAssertTrue(self.storage.isCached(forKey: "1"))
+            XCTAssertTrue(self.storage.isCached(forKey: "2"))
+            exp.fulfill()
+        }
+        waitForExpectations(timeout: 3, handler: nil)
+    }
+    
+    func testStoreWithExpirationNotExtending() {
+        let exp = expectation(description: #function)
+        
+        XCTAssertFalse(storage.isCached(forKey: "1"))
+        try! storage.store(value: 1, forKey: "1", expiration: .seconds(0.15))
+        XCTAssertTrue(storage.isCached(forKey: "1"))
+        
+        XCTAssertFalse(storage.isCached(forKey: "2"))
+        try! storage.store(value: 2, forKey: "2")
+        XCTAssertTrue(storage.isCached(forKey: "2"))
+        
+        delay(0.1) {
+            // Request for the object to not extend it's expiration date
+            let obj = self.storage.value(forKey: "1", extendingExpiration: false)
+            XCTAssertNotNil(obj)
+        }
+        
+        delay(0.2) {
+            XCTAssertFalse(self.storage.isCached(forKey: "1"))
+            XCTAssertTrue(self.storage.isCached(forKey: "2"))
+            exp.fulfill()
+        }
+        waitForExpectations(timeout: 3, handler: nil)
+    }
 
     func testRemoveExpired() {
         let exp = expectation(description: #function)