上一篇地址:Kingfisher源碼閱讀(一)
開始下載任務(wù)
上次說到了downloadAndCacheImageWithURL
這個方法,看名字就知道既要下載圖片又要緩存圖片,它的方法體是這樣的:
//下載圖片
downloader.downloadImageWithURL(URL, retrieveImageTask: retrieveImageTask, options: options,
progressBlock: { receivedSize, totalSize in
progressBlock?(receivedSize: receivedSize, totalSize: totalSize)
},
completionHandler: { image, error, imageURL, originalData in
//304 NOT MODIFIed,嘗試從緩存中取數(shù)據(jù)
if let error = error where error.code == KingfisherError.NotModified.rawValue {
// Not modified. Try to find the image from cache.
// (The image should be in cache. It should be guaranteed by the framework users.)
targetCache.retrieveImageForKey(key, options: options, completionHandler: { (cacheImage, cacheType) -> () in
completionHandler?(image: cacheImage, error: nil, cacheType: cacheType, imageURL: URL)
})
return
}
if let image = image, originalData = originalData {
targetCache.storeImage(image, originalData: originalData, forKey: key, toDisk: !options.cacheMemoryOnly, completionHandler: nil)
}
completionHandler?(image: image, error: error, cacheType: .None, imageURL: URL)
}
)
調(diào)用了downloader
的downloadImageWithURL
方法,然后在completionHandler
這個完成閉包中做緩存相關(guān)的操作,我們先不管緩存,先去ImageDownloader
(downloader
是它的一個實例)里看看downloadImageWithURL
這個方法,它是長這樣的:
//默認訪問級別,只能在模塊內(nèi)部使用
internal func downloadImageWithURL(URL: NSURL,
retrieveImageTask: RetrieveImageTask?,
options: KingfisherManager.Options,
progressBlock: ImageDownloaderProgressBlock?,
completionHandler: ImageDownloaderCompletionHandler?)
{
//retrieveImageTask為nil不return,繼續(xù)向下執(zhí)行,只是沒有記錄下載任務(wù),無法取消下載過程了
if let retrieveImageTask = retrieveImageTask where retrieveImageTask.cancelled {
return
}
let timeout = self.downloadTimeout == 0.0 ? 15.0 : self.downloadTimeout
//用于創(chuàng)建一個網(wǎng)絡(luò)請求對象,我們可以根據(jù)需要來配置請求報頭等信息。
// We need to set the URL as the load key. So before setup progress, we need to ask the `requestModifier` for a final URL.
let request = NSMutableURLRequest(URL: URL, cachePolicy: .ReloadIgnoringLocalCacheData, timeoutInterval: timeout)
request.HTTPShouldUsePipelining = true
self.requestModifier?(request)
// There is a possiblility that request modifier changed the url to `nil`
if request.URL == nil {
completionHandler?(image: nil, error: NSError(domain: KingfisherErrorDomain, code: KingfisherError.InvalidURL.rawValue, userInfo: nil), imageURL: nil, originalData: nil)
return
}
//下面的步驟一次也不執(zhí)行到話self.fetchLoads[URL]就為nil
setupProgressBlock(progressBlock, completionHandler: completionHandler, forURL: request.URL!) {(session, fetchLoad) -> Void in
let task = session.dataTaskWithRequest(request)
task.priority = options.lowPriority ? NSURLSessionTaskPriorityLow : NSURLSessionTaskPriorityDefault
task.resume()
fetchLoad.shouldDecode = options.shouldDecode
fetchLoad.scale = options.scale
retrieveImageTask?.downloadTask = task
}
}
調(diào)用setupProgressBlock
這個方法之前的部分都是發(fā)送網(wǎng)絡(luò)請求之前的處理,需要注意的地方我在注釋里也寫了,我們重點來看看setupProgressBlock
這個方法:
// A single key may have multiple callbacks. Only download once.
internal func setupProgressBlock(progressBlock: ImageDownloaderProgressBlock?, completionHandler: ImageDownloaderCompletionHandler?, forURL URL: NSURL, started: ((NSURLSession, ImageFetchLoad) -> Void)) {
//該方法用于對操作設(shè)置屏障,確保在執(zhí)行完任務(wù)后才會執(zhí)行后續(xù)操作。常用于確保線程安全性操作。
dispatch_barrier_sync(barrierQueue, { () -> Void in
//----向fetchLoads[URL](如果沒有就創(chuàng)建一個).callbacks添加一個callbackPair(下載進度回調(diào),下載完成回調(diào))
var create = false
var loadObjectForURL = self.fetchLoads[URL]
if loadObjectForURL == nil {
create = true
loadObjectForURL = ImageFetchLoad()
}
let callbackPair = (progressBlock: progressBlock, completionHander: completionHandler)
loadObjectForURL!.callbacks.append(callbackPair)
self.fetchLoads[URL] = loadObjectForURL!
//----
if create {
let session = NSURLSession(configuration: self.sessionConfiguration, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
started(session, loadObjectForURL!)
}
})
}
barrierQueue
是在初始化函數(shù)里創(chuàng)建的一個并發(fā)隊列:
public init(name: String) {
if name.isEmpty {
fatalError("[Kingfisher] You should specify a name for the downloader. A downloader with empty name is not permitted.")
}
barrierQueue = dispatch_queue_create(downloaderBarrierName + name, DISPATCH_QUEUE_CONCURRENT)
processQueue = dispatch_queue_create(imageProcessQueueName + name, DISPATCH_QUEUE_CONCURRENT)
}
這個fetchLoads
是一個以URL
為鍵,ImageFetchLoad
為值的Dictionary
,ImageFetchLoad
是ImageDownloader
中的一個內(nèi)部類,它的聲明如下:
//(下載進度回調(diào),下載完成回調(diào))元組的數(shù)組,響應(yīng)數(shù)據(jù),是否解碼,縮放尺寸。
class ImageFetchLoad {
var callbacks = [CallbackPair]()
var responseData = NSMutableData()
var shouldDecode = false
var scale = KingfisherManager.DefaultOptions.scale
}
這個類非常關(guān)鍵,我們可以看到在setupProgressBlock
先是用圖片的URL去self.fetchLoads
里取對應(yīng)的ImageFetchLoad
,如果沒有的話就以當前URL為鍵創(chuàng)建一個,然后把傳過來的progressBlock
和completionHandler
打包成一個元組,添加到ImageFetchLoad
里的callbacks
數(shù)組中。這些準備工作都完成之后就可以調(diào)用這兩句開始下載圖片了:
let session = NSURLSession(configuration: self.sessionConfiguration, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
started(session, loadObjectForURL!)
這里使用了NSURLSession
,是iOS7之后比較主流的用于網(wǎng)絡(luò)請求的API(iOS7以前多使用NSURLConnection
),然后指明了以自身實例作為delegate
,started
是一個作為參數(shù)傳入的閉包,它長什么樣在downloadImageWithURL
中調(diào)用setupProgressBlock
時其實我們已經(jīng)見過了:
setupProgressBlock(progressBlock, completionHandler: completionHandler, forURL: request.URL!) {(session, fetchLoad) -> Void in
let task = session.dataTaskWithRequest(request)
task.priority = options.lowPriority ? NSURLSessionTaskPriorityLow : NSURLSessionTaskPriorityDefault
task.resume()
fetchLoad.shouldDecode = options.shouldDecode
fetchLoad.scale = options.scale
retrieveImageTask?.downloadTask = task
}
這個過程其實就是加載一下請求,配置一下優(yōu)先級,然后開始網(wǎng)絡(luò)任務(wù)。如果retrieveImageTask
不為nil
的話就把這個網(wǎng)絡(luò)任務(wù)task
賦值給retrieveImageTask?.downloadTask
,這樣調(diào)用retrieveImageTask .cancle()
但時候就可以取消下載了。顯然按我之前的線路走下來retrieveImageTask
是有值的,但ImageDownloader
還有下面這個方法,調(diào)用downloadImageWithURL
時retrieveImageTask
這個參數(shù)為nil
,如果有人調(diào)用了這個方法的話,圖片還是能下載,但是就不能取消下載了:
public func downloadImageWithURL(URL: NSURL,
options: KingfisherManager.Options,
progressBlock: ImageDownloaderProgressBlock?,
completionHandler: ImageDownloaderCompletionHandler?)
{
downloadImageWithURL(URL,
retrieveImageTask: nil,
options: options,
progressBlock: progressBlock,
completionHandler: completionHandler)
}
下載代理
前面已經(jīng)看到ImageDownloader
指定了NSURLSession
的delegate
為自身實例,所以ImageDownloader
要遵守NSURLSessionDataDelegate
這個協(xié)議:
extension ImageDownloader: NSURLSessionDataDelegate {
我們來看幾個關(guān)鍵的函數(shù):
/**
This method is exposed since the compiler requests. Do not call it.
*/
public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if let URL = dataTask.originalRequest?.URL, fetchLoad = fetchLoadForKey(URL) {
//向fetchLoads[URL].responseData添加一條響應(yīng)數(shù)據(jù)
fetchLoad.responseData.appendData(data)
//依次調(diào)用fetchLoads[URL]中的所有過程回調(diào)
for callbackPair in fetchLoad.callbacks {
callbackPair.progressBlock?(receivedSize: Int64(fetchLoad.responseData.length), totalSize: dataTask.response!.expectedContentLength)
}
}
}
這個函數(shù)會在接收到數(shù)據(jù)的時候被調(diào)用,我們?nèi)〕鲋疤砑拥?code>fetchLoads[URL].callbacks中的progressBlock
依次執(zhí)行。
/**
This method is exposed since the compiler requests. Do not call it.
*/
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
//原始請求的URL
if let URL = task.originalRequest?.URL {
if let error = error { // Error happened
callbackWithImage(nil, error: error, imageURL: URL, originalData: nil)
} else { //Download finished without error
// We are on main queue when receiving this.
dispatch_async(processQueue, { () -> Void in
//獲取fetchLoads[URL]
if let fetchLoad = self.fetchLoadForKey(URL) {
if let image = UIImage.kf_imageWithData(fetchLoad.responseData, scale: fetchLoad.scale) {
//下載完成后可以進行的自定義操作,用戶可以自行指定delegate
self.delegate?.imageDownloader?(self, didDownloadImage: image, forURL: URL, withResponse: task.response!)
//如果指定需要解碼,則先解碼再進行完成回調(diào)
if fetchLoad.shouldDecode {
self.callbackWithImage(image.kf_decodedImage(scale: fetchLoad.scale), error: nil, imageURL: URL, originalData: fetchLoad.responseData)
} else {
self.callbackWithImage(image, error: nil, imageURL: URL, originalData: fetchLoad.responseData)
}
} else {
//不能生成圖片,返回304狀態(tài)碼,表示圖片沒有更新,可以直接使用緩存
// If server response is 304 (Not Modified), inform the callback handler with NotModified error.
// It should be handled to get an image from cache, which is response of a manager object.
if let res = task.response as? NSHTTPURLResponse where res.statusCode == 304 {
self.callbackWithImage(nil, error: NSError(domain: KingfisherErrorDomain, code: KingfisherError.NotModified.rawValue, userInfo: nil), imageURL: URL, originalData: nil)
return
}
//不能生成圖片,報BadData錯誤
self.callbackWithImage(nil, error: NSError(domain: KingfisherErrorDomain, code: KingfisherError.BadData.rawValue, userInfo: nil), imageURL: URL, originalData: nil)
}
} else {
//fatchLoads[URL] = nil,說明setupProgressBlock方法一次也沒執(zhí)行,let retrieveImageTask = retrieveImageTask where retrieveImageTask.cancelled,request.URL == nil
self.callbackWithImage(nil, error: NSError(domain: KingfisherErrorDomain, code: KingfisherError.BadData.rawValue, userInfo: nil), imageURL: URL, originalData: nil)
}
})
}
}
}
這個方法是在請求完成之后調(diào)用的,很關(guān)鍵。雖然比較長,但是思路清晰,我還略顯畫蛇添足地做了些中文注釋,應(yīng)該不用多說了(當然跟之前一樣,我覺得這里把dispatch_async
之后的那一整段邏輯提取為一個callbackWithNoErrorFor(task: NSURLSessionTask, URL: NSURL)
可讀性會更好,比較對稱)。這里多次使用到的一個callbackWithImage
的方法,我們看看它是什么樣子:
//依次調(diào)用fetchLoads[URL]中的所有完成回調(diào),并刪除該URL對應(yīng)的鍵值對
private func callbackWithImage(image: UIImage?, error: NSError?, imageURL: NSURL, originalData: NSData?) {
if let callbackPairs = fetchLoadForKey(imageURL)?.callbacks {
//就是調(diào)用了self.fetchLoads.removeValueForKey(URL)
self.cleanForURL(imageURL)
for callbackPair in callbackPairs {
callbackPair.completionHander?(image: image, error: error, imageURL: imageURL, originalData: originalData)
}
}
}
先去取跟imageURL
對應(yīng)的fetchLoad
的callbacks
,取到之后就把fetchLoads
中imageURL
的鍵值對刪掉(因為閉包元組已經(jīng)取出來了,接下來就要依次調(diào)用完成閉包,這張圖片的fetchLoad
在下載模塊中的使命已經(jīng)光榮完成),最后依次調(diào)用callbacks
中的完成閉包。
主要的委托方法都看完了,最后還有一個跟身份認證有關(guān)的:
//身份認證
/**
This method is exposed since the compiler requests. Do not call it.
*/
public func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
//一般用于SSL/TLS協(xié)議(https)
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
//在白名單中的域名做特殊處理,忽視警告
if let trustedHosts = trustedHosts where trustedHosts.contains(challenge.protectionSpace.host) {
let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
completionHandler(.UseCredential, credential)
return
}
}
//默認處理
completionHandler(.PerformDefaultHandling, nil)
}
我之前并沒有用過這個方法,查了一點資料,大概主要是用來對https
做處理的吧,trustedHosts
是ImageDownloader
里聲明的一個字符串集合,應(yīng)該就是類似于一個白名單,放到里面的域名是可以信任的。
下載模塊差不多就是這樣,小結(jié)一下知識點:
-
NSMutableURLRequest
:用于創(chuàng)建一個網(wǎng)絡(luò)請求對象,可以根據(jù)需要來配置請求報頭等信息。 -
dispatch_barrier_sync
:該方法用于對操作設(shè)置屏障,確保在執(zhí)行完任務(wù)后才會執(zhí)行后續(xù)操作,保持同步和線程安全。 - 關(guān)于
NSURLAuthenticationChallenge
的委托方法,可以使用白名單對信任的域名做特殊處理。
嗯,下期就是緩存模塊了。話說昨天給Kingfisher提了個萌萌的pull request,喵神接受了誒,喵神真是好人^ ^不過雖然我讀的是最新的版本,但fork的版本比較老了,都忘了這茬,導(dǎo)致了很多沖突,讓喵神不好merge了,真是不好意思。今天再提交一次pull request。
下一篇地址:Kingfisher源碼閱讀(三)