上一篇地址:Kingfisher源码阅读(一)
开始下载任务
上次说到了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,尝试从缓存中取数据
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)
}
)
调用了downloader
的downloadImageWithURL
方法,然后在completionHandler
这个完成闭包中做缓存相关的操作,我们先不管缓存,先去ImageDownloader
(downloader
是它的一个实例)里看看downloadImageWithURL
这个方法,它是长这样的:
//默认访问级别,只能在模块内部使用
internal func downloadImageWithURL(URL: NSURL,
retrieveImageTask: RetrieveImageTask?,
options: KingfisherManager.Options,
progressBlock: ImageDownloaderProgressBlock?,
completionHandler: ImageDownloaderCompletionHandler?)
{
//retrieveImageTask为nil不return,继续向下执行,只是没有记录下载任务,无法取消下载过程了
if let retrieveImageTask = retrieveImageTask where retrieveImageTask.cancelled {
return
}
let timeout = self.downloadTimeout == 0.0 ? 15.0 : self.downloadTimeout
//用于创建一个网络请求对象,我们可以根据需要来配置请求报头等信息。
// 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
}
//下面的步骤一次也不执行到话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
}
}
调用setupProgressBlock
这个方法之前的部分都是发送网络请求之前的处理,需要注意的地方我在注释里也写了,我们重点来看看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)) {
//该方法用于对操作设置屏障,确保在执行完任务后才会执行后续操作。常用于确保线程安全性操作。
dispatch_barrier_sync(barrierQueue, { () -> Void in
//----向fetchLoads[URL](如果没有就创建一个).callbacks添加一个callbackPair(下载进度回调,下载完成回调)
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
是在初始化函数里创建的一个并发队列:
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
中的一个内部类,它的声明如下:
//(下载进度回调,下载完成回调)元组的数组,响应数据,是否解码,缩放尺寸。
class ImageFetchLoad {
var callbacks = [CallbackPair]()
var responseData = NSMutableData()
var shouldDecode = false
var scale = KingfisherManager.DefaultOptions.scale
}
这个类非常关键,我们可以看到在setupProgressBlock
先是用图片的URL去self.fetchLoads
里取对应的ImageFetchLoad
,如果没有的话就以当前URL为键创建一个,然后把传过来的progressBlock
和completionHandler
打包成一个元组,添加到ImageFetchLoad
里的callbacks
数组中。这些准备工作都完成之后就可以调用这两句开始下载图片了:
let session = NSURLSession(configuration: self.sessionConfiguration, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
started(session, loadObjectForURL!)
这里使用了NSURLSession
,是iOS7之后比较主流的用于网络请求的API(iOS7以前多使用NSURLConnection
),然后指明了以自身实例作为delegate
,started
是一个作为参数传入的闭包,它长什么样在downloadImageWithURL
中调用setupProgressBlock
时其实我们已经见过了:
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
}
这个过程其实就是加载一下请求,配置一下优先级,然后开始网络任务。如果retrieveImageTask
不为nil
的话就把这个网络任务task
赋值给retrieveImageTask?.downloadTask
,这样调用retrieveImageTask .cancle()
但时候就可以取消下载了。显然按我之前的线路走下来retrieveImageTask
是有值的,但ImageDownloader
还有下面这个方法,调用downloadImageWithURL
时retrieveImageTask
这个参数为nil
,如果有人调用了这个方法的话,图片还是能下载,但是就不能取消下载了:
public func downloadImageWithURL(URL: NSURL,
options: KingfisherManager.Options,
progressBlock: ImageDownloaderProgressBlock?,
completionHandler: ImageDownloaderCompletionHandler?)
{
downloadImageWithURL(URL,
retrieveImageTask: nil,
options: options,
progressBlock: progressBlock,
completionHandler: completionHandler)
}
下载代理
前面已经看到ImageDownloader
指定了NSURLSession
的delegate
为自身实例,所以ImageDownloader
要遵守NSURLSessionDataDelegate
这个协议:
extension ImageDownloader: NSURLSessionDataDelegate {
我们来看几个关键的函数:
/**
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添加一条响应数据
fetchLoad.responseData.appendData(data)
//依次调用fetchLoads[URL]中的所有过程回调
for callbackPair in fetchLoad.callbacks {
callbackPair.progressBlock?(receivedSize: Int64(fetchLoad.responseData.length), totalSize: dataTask.response!.expectedContentLength)
}
}
}
这个函数会在接收到数据的时候被调用,我们取出之前添加到fetchLoads[URL].callbacks
中的progressBlock
依次执行。
/**
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!)
//如果指定需要解码,则先解码再进行完成回调
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状态码,表示图片没有更新,可以直接使用缓存
// 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方法一次也没执行,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)
}
})
}
}
}
这个方法是在请求完成之后调用的,很关键。虽然比较长,但是思路清晰,我还略显画蛇添足地做了些中文注释,应该不用多说了(当然跟之前一样,我觉得这里把dispatch_async
之后的那一整段逻辑提取为一个callbackWithNoErrorFor(task: NSURLSessionTask, URL: NSURL)
可读性会更好,比较对称)。这里多次使用到的一个callbackWithImage
的方法,我们看看它是什么样子:
//依次调用fetchLoads[URL]中的所有完成回调,并删除该URL对应的键值对
private func callbackWithImage(image: UIImage?, error: NSError?, imageURL: NSURL, originalData: NSData?) {
if let callbackPairs = fetchLoadForKey(imageURL)?.callbacks {
//就是调用了self.fetchLoads.removeValueForKey(URL)
self.cleanForURL(imageURL)
for callbackPair in callbackPairs {
callbackPair.completionHander?(image: image, error: error, imageURL: imageURL, originalData: originalData)
}
}
}
先去取跟imageURL
对应的fetchLoad
的callbacks
,取到之后就把fetchLoads
中imageURL
的键值对删掉(因为闭包元组已经取出来了,接下来就要依次调用完成闭包,这张图片的fetchLoad
在下载模块中的使命已经光荣完成),最后依次调用callbacks
中的完成闭包。
主要的委托方法都看完了,最后还有一个跟身份认证有关的:
//身份认证
/**
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协议(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
里声明的一个字符串集合,应该就是类似于一个白名单,放到里面的域名是可以信任的。
下载模块差不多就是这样,小结一下知识点:
-
NSMutableURLRequest
:用于创建一个网络请求对象,可以根据需要来配置请求报头等信息。 -
dispatch_barrier_sync
:该方法用于对操作设置屏障,确保在执行完任务后才会执行后续操作,保持同步和线程安全。 - 关于
NSURLAuthenticationChallenge
的委托方法,可以使用白名单对信任的域名做特殊处理。
嗯,下期就是缓存模块了。话说昨天给Kingfisher提了个萌萌的pull request,喵神接受了诶,喵神真是好人^ ^不过虽然我读的是最新的版本,但fork的版本比较老了,都忘了这茬,导致了很多冲突,让喵神不好merge了,真是不好意思。今天再提交一次pull request。
下一篇地址:Kingfisher源码阅读(三)