iOS中的网络编程与多线程编程
iOS中的网络编程和多线程编程
博主的寒假学习任务的第一个任务即学习多线程编程。在读《Objective-C高级编程》(里面的GCD版本有点老)的时候我了解了block和GCD多线程编程的使用方法,在网易彩票demo自定义tabbar切换场景和翻牌游戏的翻牌动画中进行了使用。私以为网络编程和多线程编程是学习iOS开发中的重点,下面来归纳整理一些相关知识。
移动互联网时代几乎所有的应用程序都要使用网络请求,而为了编写高效的网络请求模块,开发者必须能灵活运用多线程的各种操作。
多线程编程
先来简单复习一下操作系统里线程的概念:
-
线程是操作系统任务调度的基本单元,合理使用多线程才能充分利用多核CPU,合理设置优先级让重要的任务更快完成(比如主线程)。
-
线程共享一个进程内的资源,共享虚拟内存/描述符等,多个线程访问贡献资源可能会存在竞争。
-
线程有独立的调用栈/本地变量/寄存器上下文,线程的创建/销毁/切换也是有一定开销的,只不过这个开销要比进程小。
多线程指从软件或硬件上实现多个线程并发执行的技术,进而提升整体的处理性能。
iOS中实现多线程编程主要有三个方法,也有四个的说法。主要的三个指的是:NSThread、GCD和NSOperation。四个的说法是因为OC兼容了C语言,故而C语言中的POSIX接口也可以用来使用多线程,也就是引用C的头文件pthread.h(博主在学校的操作系统实验里用了这个写多线程,好麻烦…)。
多线程编程的难点主要有:多线程下操作的顺序不可预测、编译器优化会重排代码、CPU会乱序执行指令,因此不要对执行顺序妄作假设。
NSThread
NSThread是封装程度最小、最轻量级的多线程编程接口,它使用更灵活,但要手动管理线程的生命周期、线程同步和线程加锁等,开销较大。其使用比较简单,可以动态创建初始化NSTread对象。静态快速创建并开启一个新线程可以使用+detachNewThreadSelector:toTarget:withObject与+detachNewThreadWithBlock:。
NSObject基类对象提供了隐式快速创建NSThread线程的performSelector系列扩展工具类方法(如在指定线程上执行方法的performSelector:onThread:withObject:waitUntilDone:),和一些静态工具接口来控制当前线程以及获取当前线程的一些信息。
部分常用的方法包括:开启线程(start)、是否开启了多线程(+isMultiThreaded)、获取当前线程(+currentThread)、获取主线程(+mainThread)、睡眠当前进程、设置优先级(.threadPriority)等。
GCD⭐⭐⭐
GCD,即Grand Central Dispatch,又叫大中央调度(某些书会译作大中枢派发),它对线程的操作进行了封装,加入了很多新特性,内部进行了效率优化,提供了简洁的C语言接口,使用更加简单高效,也是苹果公司提供的方式。
GCD的好处包括有:GCD可用于多核的并行计算;GCD会自动利用更多的CPU内核;GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
GCD的队列主要有三种:全局并发队列、主队列和用户创建的队列,队列按并发性可分为并发队列和串行队列。主队列的本质是串行队列。注意队列和线程并不是一一对应,主线程上执行的队列可能不止一个。
队列中的任务又有执行方式的区别,分为同步执行(dispatch_async)和异步执行(dispatch_sync)。同步指阻塞当前线程,等添加的耗时任务完成后再返回,而异步则是将任务添加到队列后返回,后面的代码不需要等待添加的任务完成即可继续执行。
注意理解不同队列和不同执行方式的组合。这个比较难以理解,写起来篇幅挺长的^^,可以参考一下别的好博客。
一些常用函数:
dispatch_once_t:控制代码指定代码只执行一次,常用来实现单例模式。
dispatch_after:提交的任务从提交开始后的指定时间执行。
dispatch_group_t:组调度,实现等待一组操作都完成后执行后续操作,应用场景有比如大图片下载,分块下载再拼接。
dispatch_barrier_async:设置栅栏,栅栏后的任务一定在栅栏前的任务们完成之后执行。
队列优先级建议使用默认的default优先级。
NSOperation
NSOperation是基于GCD的一个基类,它也不需要管理线程的声明周期和同步,但比GCD可控性更强,可以指定任务依赖、设置并发数、取消任务和KVO监听任务状态。
NSOperation是一个抽象基类,使用时使用的是其两个实体子类NSBlockOperation和NSInvocationOperation(前者指定操作的Block,后者指定操作的方法)。异步执行可以加入NSOperationQueue,具体使用可以看API的说明。
线程安全
定义:当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获取正确的结果,那么这个对象是线程安全的。
iOS中线程不安全的对象,如NSMutableArray,在其说明文档中没有被注明线程安全。
只能在某个线程使用的对象:UIKit/CoreAnimation中几乎所有的对象都只能在主线程中使用,但也有例外:UIImage和UIFont。
在Xcode可以开启Thread Sanitizer来帮助检查哪些代码违反了线程安全的原则,Main Thread Checker来帮助查看是否有不安全的API使用。
同步机制
iOS里的同步机制主要有:
@synchronized:OC语言提供的内置的同步机制,使用的方法很简单,@synchronized(要同步的对象指针){}
NSLock/NSRecursiveLock:创建一个NSLock的实例,调用-lock和-unlock来上锁和开锁。注意开关锁之间是可以嵌套锁的,而NSRecursiveLock可以保证嵌套调用时不会死锁。
GCD的同步机制:同步执行、创建串行队列、组队列、阻塞任务(barrier)、以及信号量机制(dispatch_semapore)。
NSOperation同步机制:设置依赖性、最大并发数。
多线程练习小游戏:deadlockempire.github.io
网络编程
在计算机网络里我们深入了解了HTTP这一超文本传输协议,iOS开发中发送HTTP请求有以下几种方案:NSURLConnection、NSURLSession(iOS7之后逐渐取代NSURLConnection)、CFNetwork(用于TLS、TCP和UDP连接,需要iOS12+)和其他第三方网络请求框架(如OC里的AFNetworking,TTNetWorkManager)。
HTTP与HTTPS
HTTP:HTTP现在最新的是2.0版本,比起1.1版本它支持多路复用(二进制分帧)、服务端主动推送(现在推送主要有长连接和客户端轮询)、头部压缩、随时复位、优先权等。
HTTPS:HTTP协议是以明文方式发送内容,为了解决这个安全性的缺陷,HTTPS在HTTP的基础上加入了SSL加密传输协议,增加了TLS层(传输层安全),依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密,使用端口号443。
Charles抓包工具,截取HTTP和HTTPS网络数据,可以改数据。
数据格式
主要有JSON(最常用)和PB,苹果官方提供了JSON的序列化和反序列化的API。
JSON我们都熟悉,是JS的对象表示法,独立于语言和环境的轻量级文本数据交换格式,在网络传输中一般是使用GZip压缩算法。
PB则是一种比JSON体积更小、解析速度更快的结构化数据存储格式,编译期间就可保证正确性,采用标识-长度-字段值表示单个数据(T-L-V数据存储方式),缺点是服务端新增字段就需要重新生成打包文件。
NSURLSession
通过task来管理,主要有四种task:
- DataTask:数据请求
- DownloadTask:文件下载、下载进度、断点续传等
- UploadTask:文件上传
- StreamTask:TCP连接(iOS9+)
Network.framework
Network.framework具有智能建立连接、优化数据传输、进行安全加密、兼容移动网络等特性,使用参考苹果的API说明文档。
AFNetWorking
很好用的第三方框架,在GitHub上可以看到其说明文档。
TTNetworkManager
头条自研的API,暂时没有使用过,主要有如下特性:
-
具有AFNetwork和ChromeNet内核,可以动态切换
-
有细致的timing信息,可以用来排查网络瓶颈。
-
错误信息明确
-
C++跨平台源码公开
-
支持HTTPDNS(传统的DNS需要在运营商进行IP地址转换,HTTP可以在服务器存储的列表中拿到IP地址)
-
支持HTTP2.0和QUIC
-
支持选路和流控等新功能