Просмотр исходного кода

Merge branch 'master' into swift3

# Conflicts:
#	Sources/ImageDownloader.swift
#	Sources/KingfisherOptionsInfo.swift
#	Tests/KingfisherTests/KingfisherOptionsInfoTests.swift
onevcat 9 лет назад
Родитель
Сommit
22279fdf76

+ 4 - 0
Kingfisher.xcodeproj/project.pbxproj

@@ -1870,8 +1870,10 @@
 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
 				CLANG_WARN_EMPTY_BODY = YES;
 				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
 				CLANG_WARN_INT_CONVERSION = YES;
 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@@ -1916,8 +1918,10 @@
 				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
 				CLANG_WARN_EMPTY_BODY = YES;
 				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
 				CLANG_WARN_INT_CONVERSION = YES;
 				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
 				CLANG_WARN_UNREACHABLE_CODE = YES;
 				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
 				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";

+ 41 - 8
Sources/ImageDownloader.swift

@@ -78,14 +78,17 @@ private let instance = ImageDownloader(name: defaultDownloaderName)
 /**
 The error code.
 
-- BadData: The downloaded data is not an image or the data is corrupted.
-- NotModified: The remote server responsed a 304 code. No image data downloaded.
-- InvalidURL: The URL is invalid.
+- badData: The downloaded data is not an image or the data is corrupted.
+- notModified: The remote server responsed a 304 code. No image data downloaded.
+- invalidStatusCode: The HTTP status code in response is not valid. 
+- notCached: The image rquested is not in cache but .onlyFromCache is activated.
+- invalidURL: The URL is invalid.
 */
 public enum KingfisherError: Int {
     case badData = 10000
     case notModified = 10001
-    case InvalidStatusCode = 10002
+    case invalidStatusCode = 10002
+    case notCached = 10003
     case invalidURL = 20000
 }
 
@@ -100,6 +103,28 @@ public enum KingfisherError: Int {
     - parameter response:   The response object of the downloading process.
     */
     @objc optional func imageDownloader(_ downloader: ImageDownloader, didDownloadImage image: Image, forURL URL: URL, withResponse response: URLResponse)
+    
+    
+    /**
+    Check if a received HTTP status code is valid or not. 
+    By default, a status code between 200 to 400 (not included) is considered as valid.
+    If an invalid code is received, the downloader will raise an .invalidStatusCode error.
+    It has a `userInfo` which includes this statusCode and localizedString error message.
+     
+    - parameter code: The received HTTP status code.
+    
+    - returns: Whether this HTTP status code is valid or not.
+     
+    - Note: If the default 200 to 400 valid code does not suit your need, 
+            you can implement this method to change that behavior.
+    */
+    func isValidStatusCode(code: Int) -> Bool
+}
+
+extension ImageDownloaderDelegate {
+    func isValidStatusCode(code: Int) -> Bool {
+        return (200..<400).contains(code)
+    }
 }
 
 /// Protocol indicates that an authentication challenge could be handled.
@@ -368,10 +393,10 @@ class ImageDownloaderSessionHandler: NSObject, URLSessionDataDelegate, Authentic
     */
     internal func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: (URLSession.ResponseDisposition) -> Void) {
         
-        // If server response is not 200,201 or 304, inform the callback handler with InvalidStatusCode error.
-        // InvalidStatusCode error has userInfo which include statusCode and localizedString.
-        if let statusCode = (response as? HTTPURLResponse)?.statusCode, let URL = dataTask.originalRequest?.url, statusCode != 200 && statusCode != 201 && statusCode != 304 {
-            callbackWithImage(nil, error: NSError(domain: KingfisherErrorDomain, code: KingfisherError.InvalidStatusCode.rawValue, userInfo: ["statusCode": statusCode, "localizedStringForStatusCode": HTTPURLResponse.localizedString(forStatusCode: statusCode)]), imageURL: URL, originalData: nil)
+        if let statusCode = (response as? HTTPURLResponse)?.statusCode,
+                  let URL = dataTask.originalRequest?.url, !isValidStatusCode(code: statusCode)
+        {
+            callbackWithImage(nil, error: NSError(domain: KingfisherErrorDomain, code: KingfisherError.invalidStatusCode.rawValue, userInfo: ["statusCode": statusCode, "localizedStringForStatusCode": HTTPURLResponse.localizedString(forStatusCode: statusCode)]), imageURL: URL, originalData: nil)
         }
         
         completionHandler(Foundation.URLSession.ResponseDisposition.allow)
@@ -483,4 +508,12 @@ class ImageDownloaderSessionHandler: NSObject, URLSessionDataDelegate, Authentic
             }
         })
     }
+    
+    private func isValidStatusCode(code: Int) -> Bool {
+        if let delegate = downloadHolder?.delegate {
+            return delegate.isValidStatusCode(code: code)
+        } else {
+            return (200..<400).contains(code)
+        }
+    }
 }

+ 5 - 1
Sources/KingfisherManager.swift

@@ -220,6 +220,9 @@ public class KingfisherManager {
             completionHandler: { image, cacheType in
                 if image != nil {
                     diskTaskCompletionHandler(image: image, error: nil, cacheType:cacheType, imageURL: URL)
+                } else if let options = options, options.onlyFromCache {
+                    let error = NSError(domain: KingfisherErrorDomain, code: KingfisherError.notCached.rawValue, userInfo: nil)
+                    diskTaskCompletionHandler(image: nil, error: error, cacheType:.none, imageURL: URL)
                 } else {
                     self.downloadAndCacheImageWithURL(URL,
                         forKey: key,
@@ -228,7 +231,8 @@ public class KingfisherManager {
                         completionHandler: diskTaskCompletionHandler,
                         options: options)
                 }
-            })
+            }
+        )
         retrieveImageTask.diskRetrieveTask = diskTask
     }
 }

+ 7 - 0
Sources/KingfisherOptionsInfo.swift

@@ -47,6 +47,7 @@ Items could be added into KingfisherOptionsInfo.
 - ForceRefresh: If set, `Kingfisher` will ignore the cache and try to fire a download task for the resource.
 - ForceTransition: If set, setting the image to an image view will happen with transition even when retrieved from cache. See `Transition` option for more.
 - CacheMemoryOnly: If set, `Kingfisher` will only cache the value in memory but not in disk.
+- OnlyFromCache: If set, `Kingfisher` will only try to retrieve the image from cache not from network.
 - BackgroundDecode: Decode the image in background thread before using.
 - CallbackDispatchQueue: The associated value of this member will be used as the target queue of dispatch callbacks when retrieving images from cache. If not set, `Kingfisher` will use main quese for callbacks.
 - ScaleFactor: The associated value of this member will be used as the scale factor when converting retrieved data to an image.
@@ -60,6 +61,7 @@ public enum KingfisherOptionsInfoItem {
     case forceRefresh
     case forceTransition
     case cacheMemoryOnly
+    case onlyFromCache
     case backgroundDecode
     case callbackDispatchQueue(DispatchQueue?)
     case scaleFactor(CGFloat)
@@ -81,6 +83,7 @@ func <== (lhs: KingfisherOptionsInfoItem, rhs: KingfisherOptionsInfoItem) -> Boo
     case (.forceRefresh, .forceRefresh): fallthrough
     case (.forceTransition, .forceTransition): fallthrough
     case (.cacheMemoryOnly, .cacheMemoryOnly): fallthrough
+    case (.onlyFromCache, .onlyFromCache): fallthrough
     case (.backgroundDecode, .backgroundDecode): fallthrough
     case (.callbackDispatchQueue(_), .callbackDispatchQueue(_)): fallthrough
     case (.scaleFactor(_), .scaleFactor(_)): fallthrough
@@ -149,6 +152,10 @@ extension Collection where Iterator.Element == KingfisherOptionsInfoItem {
         return contains{ $0 <== .cacheMemoryOnly }
     }
     
+    var onlyFromCache: Bool {
+        return contains{ $0 <== .onlyFromCache }
+    }
+    
     var backgroundDecode: Bool {
         return contains{ $0 <== .backgroundDecode }
     }

+ 1 - 1
Tests/KingfisherTests/ImageDownloaderTests.swift

@@ -170,7 +170,7 @@ class ImageDownloaderTests: XCTestCase {
             
         }) { (image, error, imageURL, data) -> () in
             XCTAssertNotNil(error, "There should be an error since server returning 404")
-            XCTAssertEqual(error!.code, KingfisherError.InvalidStatusCode.rawValue, "The error should be InvalidStatusCode.")
+            XCTAssertEqual(error!.code, KingfisherError.invalidStatusCode.rawValue, "The error should be InvalidStatusCode.")
             XCTAssertEqual(error!.userInfo["statusCode"]! as? Int, 404, "The error should be InvalidStatusCode.")
             expectation.fulfill()
         }

+ 18 - 1
Tests/KingfisherTests/KingfisherManagerTests.swift

@@ -148,7 +148,24 @@ class KingfisherManagerTests: XCTestCase {
         })
         waitForExpectations(timeout: 5, handler: nil)
     }
-    
+
+    func testShouldNotDownloadImageIfCacheOnlyAndNotInCache() {
+        cleanDefaultCache()
+        let expectation = self.expectation(description: "wait for retrieving image cache")
+        let URLString = testKeys[0]
+        _ = stubRequest("GET", URLString).andReturn(200)?.withBody(testImageData)
+
+        let url = URL(string: URLString)!
+
+        manager.retrieveImageWithURL(url, optionsInfo: [.onlyFromCache], progressBlock: nil, completionHandler: { image, error, _, _ in
+                XCTAssertNil(image)
+                XCTAssertNotNil(error)
+                XCTAssertEqual(error!.code, KingfisherError.notCached.rawValue)
+                expectation.fulfill()
+        })
+        waitForExpectations(timeout: 5, handler: nil)
+    }
+
     func testErrorCompletionHandlerRunningOnMainQueueDefaultly() {
         let expectation = self.expectation(description: "running on main queue")
         let URLString = testKeys[0]

+ 3 - 1
Tests/KingfisherTests/KingfisherOptionsInfoTests.swift

@@ -80,9 +80,10 @@ class KingfisherOptionsInfoTests: XCTestCase {
             .downloadPriority(0.8),
             .forceRefresh,
             .cacheMemoryOnly,
+            .onlyFromCache,
             .backgroundDecode,
             .callbackDispatchQueue(queue),
-            .scaleFactor(2.0) as KingfisherOptionsInfoItem // Workaround for a bug in Swift 3 beta 2 which fails to infer the type.
+            KingfisherOptionsInfoItem.scaleFactor(2.0)
         ]
         
         XCTAssertTrue(options.targetCache === cache)
@@ -98,6 +99,7 @@ class KingfisherOptionsInfoTests: XCTestCase {
         XCTAssertEqual(options.downloadPriority, 0.8)
         XCTAssertTrue(options.forceRefresh)
         XCTAssertTrue(options.cacheMemoryOnly)
+        XCTAssertTrue(options.onlyFromCache)
         XCTAssertTrue(options.backgroundDecode)
         
         XCTAssertEqual(options.callbackDispatchQueue.label, queue.label)