ImageDownloader.swift 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. //
  2. // ImageDownloader.swift
  3. // Kingfisher
  4. //
  5. // Created by Wei Wang on 15/4/6.
  6. //
  7. // Copyright (c) 2018 Wei Wang <onevcat@gmail.com>
  8. //
  9. // Permission is hereby granted, free of charge, to any person obtaining a copy
  10. // of this software and associated documentation files (the "Software"), to deal
  11. // in the Software without restriction, including without limitation the rights
  12. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  13. // copies of the Software, and to permit persons to whom the Software is
  14. // furnished to do so, subject to the following conditions:
  15. //
  16. // The above copyright notice and this permission notice shall be included in
  17. // all copies or substantial portions of the Software.
  18. //
  19. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  20. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  21. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  22. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  23. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  24. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  25. // THE SOFTWARE.
  26. #if os(macOS)
  27. import AppKit
  28. #else
  29. import UIKit
  30. #endif
  31. /// Progress update block of downloader.
  32. public typealias ImageDownloaderProgressBlock = DownloadProgressBlock
  33. /// Completion block of downloader.
  34. public typealias ImageDownloaderCompletionHandler = ((_ image: Image?, _ error: NSError?, _ url: URL?, _ originalData: Data?) -> Void)
  35. /// Download task.
  36. public struct RetrieveImageDownloadTask {
  37. let internalTask: URLSessionDataTask
  38. /// Downloader by which this task is initialized.
  39. public private(set) weak var ownerDownloader: ImageDownloader?
  40. /// Cancel this download task. It will trigger the completion handler with an NSURLErrorCancelled error.
  41. /// If you want to cancel all downloading tasks, call `cancelAll()` of `ImageDownloader` instance.
  42. public func cancel() {
  43. ownerDownloader?.cancel(self)
  44. }
  45. /// The original request URL of this download task.
  46. public var url: URL? {
  47. return internalTask.originalRequest?.url
  48. }
  49. /// The relative priority of this download task.
  50. /// It represents the `priority` property of the internal `NSURLSessionTask` of this download task.
  51. /// The value for it is between 0.0~1.0. Default priority is value of 0.5.
  52. /// See documentation on `priority` of `NSURLSessionTask` for more about it.
  53. public var priority: Float {
  54. get {
  55. return internalTask.priority
  56. }
  57. set {
  58. internalTask.priority = newValue
  59. }
  60. }
  61. }
  62. ///The code of errors which `ImageDownloader` might encountered.
  63. public enum KingfisherError: Int {
  64. /// badData: The downloaded data is not an image or the data is corrupted.
  65. case badData = 10000
  66. /// notModified: The remote server responded a 304 code. No image data downloaded.
  67. case notModified = 10001
  68. /// The HTTP status code in response is not valid. If an invalid
  69. /// code error received, you could check the value under `KingfisherErrorStatusCodeKey`
  70. /// in `userInfo` to see the code.
  71. case invalidStatusCode = 10002
  72. /// notCached: The image requested is not in cache but .onlyFromCache is activated.
  73. case notCached = 10003
  74. /// The URL is invalid.
  75. case invalidURL = 20000
  76. /// The downloading task is cancelled before started.
  77. case downloadCancelledBeforeStarting = 30000
  78. }
  79. /// Key will be used in the `userInfo` of `.invalidStatusCode`
  80. public let KingfisherErrorStatusCodeKey = "statusCode"
  81. /// Protocol of `ImageDownloader`.
  82. public protocol ImageDownloaderDelegate: AnyObject {
  83. /**
  84. Called when the `ImageDownloader` object will start downloading an image from specified URL.
  85. - parameter downloader: The `ImageDownloader` object finishes the downloading.
  86. - parameter url: URL of the original request URL.
  87. - parameter response: The request object for the download process.
  88. */
  89. func imageDownloader(_ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?)
  90. /**
  91. Called when the `ImageDownloader` completes a downloading request with success or failure.
  92. - parameter downloader: The `ImageDownloader` object finishes the downloading.
  93. - parameter url: URL of the original request URL.
  94. - parameter response: The response object of the downloading process.
  95. - parameter error: The error in case of failure.
  96. */
  97. func imageDownloader(_ downloader: ImageDownloader, didFinishDownloadingImageForURL url: URL, with response: URLResponse?, error: Error?)
  98. /**
  99. Called when the `ImageDownloader` object successfully downloaded an image from specified URL.
  100. - parameter downloader: The `ImageDownloader` object finishes the downloading.
  101. - parameter image: Downloaded image.
  102. - parameter url: URL of the original request URL.
  103. - parameter response: The response object of the downloading process.
  104. */
  105. func imageDownloader(_ downloader: ImageDownloader, didDownload image: Image, for url: URL, with response: URLResponse?)
  106. /**
  107. Check if a received HTTP status code is valid or not.
  108. By default, a status code between 200 to 400 (excluded) is considered as valid.
  109. If an invalid code is received, the downloader will raise an .invalidStatusCode error.
  110. It has a `userInfo` which includes this statusCode and localizedString error message.
  111. - parameter code: The received HTTP status code.
  112. - parameter downloader: The `ImageDownloader` object asking for validate status code.
  113. - returns: Whether this HTTP status code is valid or not.
  114. - Note: If the default 200 to 400 valid code does not suit your need,
  115. you can implement this method to change that behavior.
  116. */
  117. func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool
  118. /**
  119. Called when the `ImageDownloader` object successfully downloaded image data from specified URL.
  120. - parameter downloader: The `ImageDownloader` object finishes data downloading.
  121. - parameter data: Downloaded data.
  122. - parameter url: URL of the original request URL.
  123. - returns: The data from which Kingfisher should use to create an image.
  124. - Note: This callback can be used to preprocess raw image data
  125. before creation of UIImage instance (i.e. decrypting or verification).
  126. */
  127. func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data?
  128. }
  129. extension ImageDownloaderDelegate {
  130. public func imageDownloader(_ downloader: ImageDownloader, willDownloadImageForURL url: URL, with request: URLRequest?) {}
  131. public func imageDownloader(_ downloader: ImageDownloader, didFinishDownloadingImageForURL url: URL, with response: URLResponse?, error: Error?) {}
  132. public func imageDownloader(_ downloader: ImageDownloader, didDownload image: Image, for url: URL, with response: URLResponse?) {}
  133. public func isValidStatusCode(_ code: Int, for downloader: ImageDownloader) -> Bool {
  134. return (200..<400).contains(code)
  135. }
  136. public func imageDownloader(_ downloader: ImageDownloader, didDownload data: Data, for url: URL) -> Data? {
  137. return data
  138. }
  139. }
  140. /// Protocol indicates that an authentication challenge could be handled.
  141. public protocol AuthenticationChallengeResponsable: AnyObject {
  142. /**
  143. Called when an session level authentication challenge is received.
  144. This method provide a chance to handle and response to the authentication challenge before downloading could start.
  145. - parameter downloader: The downloader which receives this challenge.
  146. - parameter challenge: An object that contains the request for authentication.
  147. - parameter completionHandler: A handler that your delegate method must call.
  148. - Note: This method is a forward from `URLSessionDelegate.urlSession(:didReceiveChallenge:completionHandler:)`. Please refer to the document of it in `URLSessionDelegate`.
  149. */
  150. func downloader(_ downloader: ImageDownloader, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
  151. /**
  152. Called when an session level authentication challenge is received.
  153. This method provide a chance to handle and response to the authentication challenge before downloading could start.
  154. - parameter downloader: The downloader which receives this challenge.
  155. - parameter task: The task whose request requires authentication.
  156. - parameter challenge: An object that contains the request for authentication.
  157. - parameter completionHandler: A handler that your delegate method must call.
  158. - Note: This method is a forward from `URLSessionTaskDelegate.urlSession(:task:didReceiveChallenge:completionHandler:)`. Please refer to the document of it in `URLSessionTaskDelegate`.
  159. */
  160. func downloader(_ downloader: ImageDownloader, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
  161. }
  162. extension AuthenticationChallengeResponsable {
  163. func downloader(_ downloader: ImageDownloader, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  164. if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
  165. if let trustedHosts = downloader.trustedHosts, trustedHosts.contains(challenge.protectionSpace.host) {
  166. let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
  167. completionHandler(.useCredential, credential)
  168. return
  169. }
  170. }
  171. completionHandler(.performDefaultHandling, nil)
  172. }
  173. func downloader(_ downloader: ImageDownloader, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  174. completionHandler(.performDefaultHandling, nil)
  175. }
  176. }
  177. /// `ImageDownloader` represents a downloading manager for requesting the image with a URL from server.
  178. open class ImageDownloader {
  179. class ImageFetchLoad {
  180. var contents = [(callback: CallbackPair, options: KingfisherOptionsInfo)]()
  181. var responseData = NSMutableData()
  182. var downloadTaskCount = 0
  183. var downloadTask: RetrieveImageDownloadTask?
  184. var cancelSemaphore: DispatchSemaphore?
  185. }
  186. // MARK: - Public property
  187. /// The duration before the download is timeout. Default is 15 seconds.
  188. open var downloadTimeout: TimeInterval = 15.0
  189. /// A set of trusted hosts when receiving server trust challenges. A challenge with host name contained in this set will be ignored.
  190. /// You can use this set to specify the self-signed site. It only will be used if you don't specify the `authenticationChallengeResponder`.
  191. /// If `authenticationChallengeResponder` is set, this property will be ignored and the implementation of `authenticationChallengeResponder` will be used instead.
  192. open var trustedHosts: Set<String>?
  193. /// Use this to set supply a configuration for the downloader. By default, NSURLSessionConfiguration.ephemeralSessionConfiguration() will be used.
  194. /// You could change the configuration before a downloading task starts. A configuration without persistent storage for caches is requested for downloader working correctly.
  195. open var sessionConfiguration = URLSessionConfiguration.ephemeral {
  196. didSet {
  197. session?.invalidateAndCancel()
  198. session = URLSession(configuration: sessionConfiguration, delegate: sessionHandler, delegateQueue: nil)
  199. }
  200. }
  201. /// Whether the download requests should use pipline or not. Default is false.
  202. open var requestsUsePipelining = false
  203. fileprivate let sessionHandler: ImageDownloaderSessionHandler
  204. fileprivate var session: URLSession?
  205. /// Delegate of this `ImageDownloader` object. See `ImageDownloaderDelegate` protocol for more.
  206. open weak var delegate: ImageDownloaderDelegate?
  207. /// A responder for authentication challenge.
  208. /// Downloader will forward the received authentication challenge for the downloading session to this responder.
  209. open weak var authenticationChallengeResponder: AuthenticationChallengeResponsable?
  210. // MARK: - Internal property
  211. let barrierQueue: DispatchQueue
  212. let processQueue: DispatchQueue
  213. let cancelQueue: DispatchQueue
  214. typealias CallbackPair = (progressBlock: ImageDownloaderProgressBlock?, completionHandler: ImageDownloaderCompletionHandler?)
  215. var fetchLoads = [URL: ImageFetchLoad]()
  216. // MARK: - Public method
  217. /// The default downloader.
  218. public static let `default` = ImageDownloader(name: "default")
  219. /**
  220. Init a downloader with name.
  221. - parameter name: The name for the downloader. It should not be empty.
  222. */
  223. public init(name: String) {
  224. if name.isEmpty {
  225. fatalError("[Kingfisher] You should specify a name for the downloader. A downloader with empty name is not permitted.")
  226. }
  227. barrierQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Barrier.\(name)", attributes: .concurrent)
  228. processQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process.\(name)", attributes: .concurrent)
  229. cancelQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Cancel.\(name)")
  230. sessionHandler = ImageDownloaderSessionHandler(name: name)
  231. // Provide a default implement for challenge responder.
  232. authenticationChallengeResponder = sessionHandler
  233. session = URLSession(configuration: sessionConfiguration, delegate: sessionHandler, delegateQueue: .main)
  234. }
  235. deinit {
  236. session?.invalidateAndCancel()
  237. }
  238. func fetchLoad(for url: URL) -> ImageFetchLoad? {
  239. var fetchLoad: ImageFetchLoad?
  240. barrierQueue.sync(flags: .barrier) { fetchLoad = fetchLoads[url] }
  241. return fetchLoad
  242. }
  243. /**
  244. Download an image with a URL and option.
  245. - parameter url: Target URL.
  246. - parameter retrieveImageTask: The task to cooperate with cache. Pass `nil` if you are not trying to use downloader and cache.
  247. - parameter options: The options could control download behavior. See `KingfisherOptionsInfo`.
  248. - parameter progressBlock: Called when the download progress updated.
  249. - parameter completionHandler: Called when the download progress finishes.
  250. - returns: A downloading task. You could call `cancel` on it to stop the downloading process.
  251. */
  252. @discardableResult
  253. open func downloadImage(with url: URL,
  254. retrieveImageTask: RetrieveImageTask? = nil,
  255. options: KingfisherOptionsInfo? = nil,
  256. progressBlock: ImageDownloaderProgressBlock? = nil,
  257. completionHandler: ImageDownloaderCompletionHandler? = nil) -> RetrieveImageDownloadTask?
  258. {
  259. if let retrieveImageTask = retrieveImageTask, retrieveImageTask.cancelledBeforeDownloadStarting {
  260. completionHandler?(nil, NSError(domain: KingfisherErrorDomain, code: KingfisherError.downloadCancelledBeforeStarting.rawValue, userInfo: nil), nil, nil)
  261. return nil
  262. }
  263. let timeout = self.downloadTimeout == 0.0 ? 15.0 : self.downloadTimeout
  264. // We need to set the URL as the load key. So before setup progress, we need to ask the `requestModifier` for a final URL.
  265. var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: timeout)
  266. request.httpShouldUsePipelining = requestsUsePipelining
  267. if let modifier = options?.modifier {
  268. guard let r = modifier.modified(for: request) else {
  269. completionHandler?(nil, NSError(domain: KingfisherErrorDomain, code: KingfisherError.downloadCancelledBeforeStarting.rawValue, userInfo: nil), nil, nil)
  270. return nil
  271. }
  272. request = r
  273. }
  274. // There is a possibility that request modifier changed the url to `nil` or empty.
  275. guard let url = request.url, !url.absoluteString.isEmpty else {
  276. completionHandler?(nil, NSError(domain: KingfisherErrorDomain, code: KingfisherError.invalidURL.rawValue, userInfo: nil), nil, nil)
  277. return nil
  278. }
  279. var downloadTask: RetrieveImageDownloadTask?
  280. setup(progressBlock: progressBlock, with: completionHandler, for: url, options: options) {(session, fetchLoad) -> Void in
  281. if fetchLoad.downloadTask == nil {
  282. let dataTask = session.dataTask(with: request)
  283. fetchLoad.downloadTask = RetrieveImageDownloadTask(internalTask: dataTask, ownerDownloader: self)
  284. dataTask.priority = options?.downloadPriority ?? URLSessionTask.defaultPriority
  285. self.delegate?.imageDownloader(self, willDownloadImageForURL: url, with: request)
  286. dataTask.resume()
  287. // Hold self while the task is executing.
  288. self.sessionHandler.downloadHolder = self
  289. }
  290. fetchLoad.downloadTaskCount += 1
  291. downloadTask = fetchLoad.downloadTask
  292. retrieveImageTask?.downloadTask = downloadTask
  293. }
  294. return downloadTask
  295. }
  296. }
  297. // MARK: - Download method
  298. extension ImageDownloader {
  299. // A single key may have multiple callbacks. Only download once.
  300. func setup(progressBlock: ImageDownloaderProgressBlock?, with completionHandler: ImageDownloaderCompletionHandler?, for url: URL, options: KingfisherOptionsInfo?, started: @escaping ((URLSession, ImageFetchLoad) -> Void)) {
  301. func prepareFetchLoad() {
  302. barrierQueue.sync(flags: .barrier) {
  303. let loadObjectForURL = fetchLoads[url] ?? ImageFetchLoad()
  304. let callbackPair = (progressBlock: progressBlock, completionHandler: completionHandler)
  305. loadObjectForURL.contents.append((callbackPair, options ?? KingfisherEmptyOptionsInfo))
  306. fetchLoads[url] = loadObjectForURL
  307. if let session = session {
  308. started(session, loadObjectForURL)
  309. }
  310. }
  311. }
  312. if let fetchLoad = fetchLoad(for: url), fetchLoad.downloadTaskCount == 0 {
  313. if fetchLoad.cancelSemaphore == nil {
  314. fetchLoad.cancelSemaphore = DispatchSemaphore(value: 0)
  315. }
  316. cancelQueue.async {
  317. _ = fetchLoad.cancelSemaphore?.wait(timeout: .distantFuture)
  318. fetchLoad.cancelSemaphore = nil
  319. prepareFetchLoad()
  320. }
  321. } else {
  322. prepareFetchLoad()
  323. }
  324. }
  325. private func cancelTaskImpl(_ task: RetrieveImageDownloadTask, fetchLoad: ImageFetchLoad? = nil, ignoreTaskCount: Bool = false) {
  326. func getFetchLoad(from task: RetrieveImageDownloadTask) -> ImageFetchLoad? {
  327. guard let URL = task.internalTask.originalRequest?.url,
  328. let imageFetchLoad = self.fetchLoads[URL] else
  329. {
  330. return nil
  331. }
  332. return imageFetchLoad
  333. }
  334. guard let imageFetchLoad = fetchLoad ?? getFetchLoad(from: task) else {
  335. return
  336. }
  337. imageFetchLoad.downloadTaskCount -= 1
  338. if ignoreTaskCount || imageFetchLoad.downloadTaskCount == 0 {
  339. task.internalTask.cancel()
  340. }
  341. }
  342. func cancel(_ task: RetrieveImageDownloadTask) {
  343. barrierQueue.sync(flags: .barrier) { cancelTaskImpl(task) }
  344. }
  345. /// Cancel all downloading tasks. It will trigger the completion handlers for all not-yet-finished
  346. /// downloading tasks with an NSURLErrorCancelled error.
  347. ///
  348. /// If you need to only cancel a certain task, call `cancel()` on the `RetrieveImageDownloadTask`
  349. /// returned by the downloading methods.
  350. public func cancelAll() {
  351. barrierQueue.sync(flags: .barrier) {
  352. fetchLoads.forEach { v in
  353. let fetchLoad = v.value
  354. guard let task = fetchLoad.downloadTask else { return }
  355. cancelTaskImpl(task, fetchLoad: fetchLoad, ignoreTaskCount: true)
  356. }
  357. }
  358. }
  359. }
  360. // MARK: - NSURLSessionDataDelegate
  361. /// Delegate class for `NSURLSessionTaskDelegate`.
  362. /// The session object will hold its delegate until it gets invalidated.
  363. /// If we use `ImageDownloader` as the session delegate, it will not be released.
  364. /// So we need an additional handler to break the retain cycle.
  365. // See https://github.com/onevcat/Kingfisher/issues/235
  366. final class ImageDownloaderSessionHandler: NSObject, URLSessionDataDelegate, AuthenticationChallengeResponsable {
  367. private let downloaderQueue: DispatchQueue
  368. // The holder will keep downloader not released while a data task is being executed.
  369. // It will be set when the task started, and reset when the task finished.
  370. private var _downloadHolder: ImageDownloader?
  371. var downloadHolder: ImageDownloader? {
  372. get {
  373. return downloaderQueue.sync { _downloadHolder }
  374. }
  375. set {
  376. downloaderQueue.sync { _downloadHolder = newValue }
  377. }
  378. }
  379. init(name: String) {
  380. downloaderQueue = DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.SessionHandler.\(name)")
  381. super.init()
  382. }
  383. func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
  384. guard let downloader = downloadHolder else {
  385. completionHandler(.cancel)
  386. return
  387. }
  388. var disposition = URLSession.ResponseDisposition.allow
  389. if let statusCode = (response as? HTTPURLResponse)?.statusCode,
  390. let url = dataTask.originalRequest?.url,
  391. !(downloader.delegate ?? downloader).isValidStatusCode(statusCode, for: downloader)
  392. {
  393. let error = NSError(domain: KingfisherErrorDomain,
  394. code: KingfisherError.invalidStatusCode.rawValue,
  395. userInfo: [KingfisherErrorStatusCodeKey: statusCode, NSLocalizedDescriptionKey: HTTPURLResponse.localizedString(forStatusCode: statusCode)])
  396. // Needs to be called before callCompletionHandlerFailure() because it removes downloadHolder
  397. if let downloader = downloadHolder {
  398. downloader.delegate?.imageDownloader(downloader, didFinishDownloadingImageForURL: url, with: response, error: error)
  399. }
  400. callCompletionHandlerFailure(error: error, url: url)
  401. disposition = .cancel
  402. }
  403. completionHandler(disposition)
  404. }
  405. func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
  406. guard let downloader = downloadHolder else {
  407. return
  408. }
  409. if let url = dataTask.originalRequest?.url, let fetchLoad = downloader.fetchLoad(for: url) {
  410. fetchLoad.responseData.append(data)
  411. if let expectedLength = dataTask.response?.expectedContentLength {
  412. for content in fetchLoad.contents {
  413. DispatchQueue.main.async {
  414. content.callback.progressBlock?(Int64(fetchLoad.responseData.length), expectedLength)
  415. }
  416. }
  417. }
  418. }
  419. }
  420. func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
  421. guard let url = task.originalRequest?.url else {
  422. return
  423. }
  424. if let downloader = downloadHolder {
  425. downloader.delegate?.imageDownloader(downloader, didFinishDownloadingImageForURL: url, with: task.response, error: error)
  426. }
  427. guard error == nil else {
  428. callCompletionHandlerFailure(error: error!, url: url)
  429. return
  430. }
  431. processImage(for: task, url: url)
  432. }
  433. /**
  434. This method is exposed since the compiler requests. Do not call it.
  435. */
  436. func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  437. guard let downloader = downloadHolder else {
  438. return
  439. }
  440. downloader.authenticationChallengeResponder?.downloader(downloader, didReceive: challenge, completionHandler: completionHandler)
  441. }
  442. func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
  443. guard let downloader = downloadHolder else {
  444. return
  445. }
  446. downloader.authenticationChallengeResponder?.downloader(downloader, task: task, didReceive: challenge, completionHandler: completionHandler)
  447. }
  448. private func cleanFetchLoad(for url: URL) {
  449. guard let downloader = downloadHolder else {
  450. return
  451. }
  452. downloader.barrierQueue.sync(flags: .barrier) {
  453. downloader.fetchLoads.removeValue(forKey: url)
  454. if downloader.fetchLoads.isEmpty {
  455. downloadHolder = nil
  456. }
  457. }
  458. }
  459. private func callCompletionHandlerFailure(error: Error, url: URL) {
  460. guard let downloader = downloadHolder, let fetchLoad = downloader.fetchLoad(for: url) else {
  461. return
  462. }
  463. // We need to clean the fetch load first, before actually calling completion handler.
  464. cleanFetchLoad(for: url)
  465. var leftSignal: Int
  466. repeat {
  467. leftSignal = fetchLoad.cancelSemaphore?.signal() ?? 0
  468. } while leftSignal != 0
  469. for content in fetchLoad.contents {
  470. content.options.callbackDispatchQueue.safeAsync {
  471. content.callback.completionHandler?(nil, error as NSError, url, nil)
  472. }
  473. }
  474. }
  475. private func processImage(for task: URLSessionTask, url: URL) {
  476. guard let downloader = downloadHolder else {
  477. return
  478. }
  479. // We are on main queue when receiving this.
  480. downloader.processQueue.async {
  481. guard let fetchLoad = downloader.fetchLoad(for: url) else {
  482. return
  483. }
  484. self.cleanFetchLoad(for: url)
  485. let data: Data?
  486. let fetchedData = fetchLoad.responseData as Data
  487. if let delegate = downloader.delegate {
  488. data = delegate.imageDownloader(downloader, didDownload: fetchedData, for: url)
  489. } else {
  490. data = fetchedData
  491. }
  492. // Cache the processed images. So we do not need to re-process the image if using the same processor.
  493. // Key is the identifier of processor.
  494. var imageCache: [String: Image] = [:]
  495. for content in fetchLoad.contents {
  496. let options = content.options
  497. let completionHandler = content.callback.completionHandler
  498. let callbackQueue = options.callbackDispatchQueue
  499. let processor = options.processor
  500. var image = imageCache[processor.identifier]
  501. if let data = data, image == nil {
  502. image = processor.process(item: .data(data), options: options)
  503. // Add the processed image to cache.
  504. // If `image` is nil, nothing will happen (since the key is not existing before).
  505. imageCache[processor.identifier] = image
  506. }
  507. if let image = image {
  508. downloader.delegate?.imageDownloader(downloader, didDownload: image, for: url, with: task.response)
  509. let imageModifier = options.imageModifier
  510. let finalImage = imageModifier.modify(image)
  511. if options.backgroundDecode {
  512. let decodedImage = finalImage.kf.decoded
  513. callbackQueue.safeAsync { completionHandler?(decodedImage, nil, url, data) }
  514. } else {
  515. callbackQueue.safeAsync { completionHandler?(finalImage, nil, url, data) }
  516. }
  517. } else {
  518. if let res = task.response as? HTTPURLResponse , res.statusCode == 304 {
  519. let notModified = NSError(domain: KingfisherErrorDomain, code: KingfisherError.notModified.rawValue, userInfo: nil)
  520. completionHandler?(nil, notModified, url, nil)
  521. continue
  522. }
  523. let badData = NSError(domain: KingfisherErrorDomain, code: KingfisherError.badData.rawValue, userInfo: nil)
  524. callbackQueue.safeAsync { completionHandler?(nil, badData, url, nil) }
  525. }
  526. }
  527. }
  528. }
  529. }
  530. // Placeholder. For retrieving extension methods of ImageDownloaderDelegate
  531. extension ImageDownloader: ImageDownloaderDelegate {}