MENU

iOS 网络基础

Cover

长期以来我们在处理 iOS 中的网络通信的时候都是直接使用 OC 版 AFNetworking 或者 Swift 版 Alamofire ,但是我们竟然很少会去认真关注 iOS 中的网络基础底层内容。这同时折射出了 iOS 开发中的一个问题:iOS 开发有着丰富的组件、完善的生态、成熟的机制,这些特质让 iOS 开发的门槛低的几乎只要一台 MBP,但这也让开发人员思维退化慢慢的变成了一名搬砖工。这篇文章将介绍 iOS 底层网络通信的基础内容。

URLSession

从服务器获取数据、更新个人资料、下载或者上传文件,这些App中功能都是通过 HTTP 请求进行实现的。为了方便开发者对 HTTP 进行处理,Apple为 我们提供了 URLSession 组件。URLSession 是 iOS7 之后引入的底层网络通信架构,在此之前使用的是 NSURLConnection(iOS9 中已废弃)。其实 URLSession 除了指代同名类之外,更大程度上应该表达为整套的网络架构相关的类。URLSession 包含了 NSURLConnection 时代就有的 URLRequest 与 URLCache,但是它将 NSURLConnection 进行了分解。分解后的结构如下图:

URLSession

URLSessionDelegate

URLSessionDelegate 只负责处理 Session 级别的事务,诸如服务器信任,客户端证书的评估。该 delegate 并不是必须的,如果开发者没有自己设置 delegate 系统会完成默认设置但是必须提供一个 completionHandler 来进行数据处理。

另一个需要注意的是:不同于我们常用的编程规范,session 对 delegate 保持这强引用而不是 weak。这表明此处的代码设计会涉及到内存泄漏的问题,所以我们要小心处理。处理办法是使用单例模式或者通过 finishTasksAndInvalidate()invalidateAndCancel() 方法来使 session 失效,调用后者会立刻生效,而前者则会等待 session 明确完成或者失败后才会失效。至于这两个方法的调用时机,你可以在 URLSessionDelegate 或者 URLSessionDataDelegate 及其子类的某个方法中,也可以在控制器的 viewWillDisappear 中调用。不过我建议使用后一种处理方式,因为一个控制器可能存在多个请求任务,如果每个 Task 都销毁 Session 也就表明同一个 Session 在每个 Task 开始的时候都需要初始化,这不仅导致代码冗余也降低了代码效率。

URLSessionConfiguration

URLSessionConfiguration 对象用于对 URLSession 对象进行初始化配置。这也是 URLSession 与 NSURLConnection 相比最明显的改进之一,我们可以配置每个 session 的缓存,协议,cookie,以及证书策略(credential policy),甚至跨程序共享这些信息。这将允许程序和网络基础框架之间相互独立,不会发生干扰。

URLSession 对象中的 configuration 属性,其实是在进行初始化时 URLSessionConfiguration 对象的拷贝,并且该属性为只读属性。也就是说每个 URLSession 对象只在初始化时配置 configuration,之后都不会发生改变。

URLSessionConfiguration 有三类工厂方法:

  • default:返回标准的 configuration,使用了全局的磁盘缓存(除了文件下载任务外)、钥匙串中的证书、Cookies。

  • ephemeral:类似于default,但是所有的数据都存储在内存中,不会对缓存、Cookie 和证书进行持久化存储。可以将这个配置看作是私有 session,对于实现像秘密浏览功能来说是非常理想的。

  • background:该配置会创建一个后台 session。后台 session 不同于常规情况,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传、下载任务。

URLSessionConfiguration 还有如:超时时间、缓存策略、Cookie 策略、安全策略等属性的设置,具体的配置选项可以自行查看文档

URLSessionTask

URLSessionTask 是一个抽象类,它表示一次网络任务。URLSession 通过创建不同的 URLSessionTask 对象来处理各种不同的网络请求任务,所以 URLSessionTask 有多个类型的子类:

  • URLSessionDataTask:该类用于处理 HTTP 中 GET、POST 方法获取的数据

  • URLSessionUploadTask:该类用于处理 HTTP 中 POST、PUT 方法上传数据到服务器

  • URLSessionDownloadTask:该类用于处理从服务器下载文件的网络任务

  • URLSessionStreamTask:该类用于处理 TCP/IP 连接的网络任务(新增的类型,未使用过。猜测用于视频、IM 类应用)

所有这些任务都可以挂起、开始、取消,下载任务甚至可以实现断点续传。这些 task 获得的数据的处理方式可以通过 completionHandler 闭包或者 delegate 中的方法,但是二者取其一。

Data task 可以通过 URL 或 URLRequest 创建(使用前者相当于是使用一个对于该 URL 进行标准 GET 请求的 URLRequest,这是一种快捷方法)。下面示例为从 itunes 中查询某个关键词的歌曲:

let expectedCharSet = CharacterSet.urlQueryAllowed
let searchTerm = searchBar.text!.addingPercentEncoding(withAllowedCharacters: expectedCharSet)!

let defaultSession = URLSession.shared
let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=\(searchTerm)")

dataTask = defaultSession.dataTask(with: url!, completionHandler: {
    data, response, error in
    ...
}) 

dataTask?.resume()

上传、下载文件的代码与上面的基本一样,除了新建 dataTask 的类型不同。

总结

总体来说苹果对 URLSession 体系的设计这套体系清晰、简洁、高效,也是我们在日常编码过程需要认真学习的。作为 iOS 程序员除了努力搬砖外,更应该认真对待这些设计思想,不要成为依靠 cocoapods 生存的浮萍。所以在使用轮子的时候,一定不能忘了构建轮子的基本知识,这些基础才是程序员生根的关键。iOS 只不过是表明了当前我们专注的领域,而程序员才是我们真正的职业。