很久没有更博客,最近都在忙着毕业设计和一些小项目,所以学习精力有些分散,读源码的时间大幅度缩水。是时候继续更新了。
在解读 Operation 部分的源码之前,需要先了解一下关于 NSURLSession
的一些知识。
对于 NSURLSession 的一些知识
NSURLSession
是于 2013 年随着 iOS 7 一同面世的,苹果公司对于其的定位是作为 NSURLConnection
的替代者,然后逐步推广。现在最广泛使用的第三方网络框架 AFNetworking
以及这套博文分析的 SDWebImage
等等都在使用 NSURLSession
。
在 OSI 计算机网络体系结构中,自外向里的第三层会话层,我们可以将 NSURLSession 理解为会话层。这一层通常用于管理网络接口的创建、维护、删除等等工作。
NSURLSession
和 NSURLConnection
都提供了与各种协议,例如 HTTP 和 HTTPS,进行交互的 API。在官方文档中对其的描述,这是一种高度可配置的 Container
,通过其提供的 API 可以进行很细微的管理控制。NSURLSession
提供了 NSURLConnection
中的所有特性,在功能上可以称之为后者的超集。
使用 NSURLSession
最基本单元就是 Task
,这个是 NSURLSessionTask
的实例。有三种类型的任务:NSURLSessionDataTask
、NSURLSessionDownloadTask
和 NSURLSessionUploadTask
。
我们使用 NSURLSessionDownloadTask
来创建一个下载 Task
。
通过此例我们了解了 NSURLSession 用于网络会话层工作。是网络接口的管理层对象,在网络数据传输中,起到桥梁的作用。
Operation 的 start 开启方法
在 Downloader 一文中,我们知道 SDWebImage 下载图片是通过构造一个 Operation(NSOperation) 来实现的,并且会追加放入 downloadQueue ( NSOperationQueue
)中。所以下载任务用实例化描述,即一个 Operation。
NSOperation 一般是用来操作或者执行一个单一的任务,如果任务不复杂,其实是可以使用 Cocoa 中的 NSOperation 的派生类 NSBlockOperation
和 NSInvocationOperation
。当其无法满足需求时,我们可以像 SDWebImage 一样去定制封装 NSOperation 的子类。关于 NSOperation,又可以归为两类:并发(concurrent) 和 非并发(non-concurrent),而在 SDWebImage 中可视作并发类型。
来看 SDWebImage 中对于 start
函数的重写:
对于一个并发类 NSOperation ,在重写 start
方法时,需要去实现异步(asynchronous) 方式来处理事件。在 SDWebImage 中,可以看到类的成员对象中的 self.thread
来承载 operation 任务的执行动作。并且还处理了后台运行时的状态。
我们来具体剖析一下这段代码:
在 NSOperation
中共有三个状态,这些状态可以及时的判断 SDWebImageDownloaderOperation
是否被取消了。如果取消,则认为该任务已经完成,并且需要及时回收资源,即 reset
方法。使用 NSOperation
需要手动管理以下三个状态:
isExecuting
- 代表任务正在执行中
isFinished
- 代表任务已经完成
isCancelled
- 代表任务已经取消执行
接下来一段宏中的代码,以考虑到 App 进入后台中的发生的事。在 SD 中使用了 beginBackgroundTaskWithExpirationHandler:
来申请 App 进入后台后额外的占用时间,所以我们要拿出 UIApplication
这个类,并使用 [UIApplication sharedApplication]
这个单例来调用对应方法。考虑到 iOS 的通用性和版本问题,SD 在调用该单例时进行了双重检测:
再之后是系统后台任务的代码,这里来聊一聊 beginBackgroundTaskWithExpirationHandler
这个回调。beginBackgroundTaskWithExpirationHandler
不是意味着立即执行后台任务,它相当于注册了一个后台任务,而之后的 handler
表示 App 在直到后台运行的时机到来后在运行其中的 block 代码逻辑。所以我们仍旧需要判断下载任务的下载状态,如果下载任务还在进行,就需要取消该任务(cancel
方法)。这个方法也是在 SDWebImageDownloaderOperation
中定义的,下午将会介绍。
在做完进入后台情况的处理,也就是图片的“善后处理”之后,进入图片下载的正题部分。下载一个单文件对应的是一次网络请求。所以需要用 NSURLSession
来创建一个 task 处理这个请求。
首先取出 session 成员,因为我们需要下载多个图片,不需要为每次请求都进行握手操作,所有复用 NSURLSession
对象。如果发现其未初始化,则对其重新配置。在构造方法中,选用 defaultSessionConfiguration
,这个是默认的 session 配置,类似于 NSURLConnection
的标准配置,使用硬盘来存储缓存数据。之后创建请求,增加标记,获取当前线程。
任务取消 cancel 方法
在 cancel
方法中,会有两种处理手段。如果下载任务处于开启状态,则在该实例的持有进程中调用 cancelInternalAndStop
方法,否则的话则在当前进程调用 cancelInternal
方法。我们来看这两个方法的区别和联系。
也许你会有疑问,为什么 cancelInternalAndStop
在调用 cancelInternal
之前多此一举的判断了 self.isFinished
标志符的状态?为什么不写成一个方法?其实这是有历史原因的。请看这个链接。其中 L155 我们发现了“惊天的秘密”。这里大概讲述一下这个历史原因:在 SDWebImage 的 v3.7.0
版本及以前,并没有引入 NSURLSession
而是采用的 NSURLConnection
。而后者往往是需要与 Runloop 协同使用,因为每个 Connect 会作为一个 Source 添加到当前线程所在的 Runloop 中,并且 Runloop 会对这个 Connect 对象强引用,以保证代理方法可以调用。
在新版本中,由于启用了 NSURLSession
,说明 SDWebImage 已经放弃了 iOS 6 及以下的版本。在进行网络请求的处理时更加的安全,这也是历史的必然趋势。当然,笔者也十分开心,因为不用再解读 Runloop 的代码了。😄
SDWebImageManager 中的 NSURLSessionDataDelegate 代理方法实现
NSURLSessionDataDelegate
代理用于实现数据下载的各种回调。在 SD 中由于要处理图片下载的各种状态,所以需要遵循改代理,并去自行管理代理方法返回结果的不同处理。
在 Response
数据反馈后,都会传给客户端一个 Http 状态码,根据状态码的不同,需要执行不同情况的处理方法。在 NSURLSessionDataDelegate
的代理方法中,即可实现判断状态码的步骤:
通过 Response
反馈的 Http 状态码,做出了各种操作。当然这里只要判断出状态码,将各个操作对应上即可。而下面的规划进度回调方案中,则是整个回调方法处理图像的核心部分:
主要的过程在注释中都有讲述,这里主要说一下注释中标明的一些地方:
创建图像空间的函数原型和参数定义
当调用这个函数的时候,Quartz 创建一个一个位图绘制环境,也就是位图上下文。当你向上下文中绘制信息时,Quartz 把你要绘制的信息作为位图数据绘制到指定的内存块。
imageIO 简介
之前也许你会惊讶于 SD 库对于图片下载进度的处理,其实这些处理都是交给了 Apple 的 Core Graphics 中的 imageIO 部分组件。在处理进度其实是 imageIO 的渐进加载图片功能,这里献上官方文档。渐进加载图片的过程,只需要创建一个 imageSource 引用即可完成。在上面的源码中也是如此实现的。
对于渐进加载,现在已经有很多解决方法。例如 YYWebImage
中已经支持了多种渐进式图片加载方案,而不是传统的 baseline
方式,文章链接。
总结
SD 前面的所有流程,其实都在围绕着这个 Operation 展开的。在 Operation 中处理了关键的网络请求及下载部分,而且其会话的控制全部由 Operation 进行持有和处理。这里关系到多线程和网络的基础知识,如果想进一步了解其实现原理,可以补充一下基础知识。
引文
imageIO—完成渐进加载图片
认识 Operation