目录

iOS中的网络编程与多线程编程

iOS中的网络编程和多线程编程

​ 博主的寒假学习任务的第一个任务即学习多线程编程。在读《Objective-C高级编程》(里面的GCD版本有点老)的时候我了解了block和GCD多线程编程的使用方法,在网易彩票demo自定义tabbar切换场景和翻牌游戏的翻牌动画中进行了使用。私以为网络编程和多线程编程是学习iOS开发中的重点,下面来归纳整理一些相关知识。

​ 移动互联网时代几乎所有的应用程序都要使用网络请求,而为了编写高效的网络请求模块,开发者必须能灵活运用多线程的各种操作。

多线程编程

​ 先来简单复习一下操作系统里线程的概念:

  1. 线程是操作系统任务调度的基本单元,合理使用多线程才能充分利用多核CPU,合理设置优先级让重要的任务更快完成(比如主线程)。

  2. 线程共享一个进程内的资源,共享虚拟内存/描述符等,多个线程访问贡献资源可能会存在竞争。

  3. 线程有独立的调用栈/本地变量/寄存器上下文,线程的创建/销毁/切换也是有一定开销的,只不过这个开销要比进程小。

    多线程指从软件或硬件上实现多个线程并发执行的技术,进而提升整体的处理性能。

​ 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:

  1. DataTask:数据请求
  2. DownloadTask:文件下载、下载进度、断点续传等
  3. UploadTask:文件上传
  4. StreamTask:TCP连接(iOS9+)

Network.framework

​ Network.framework具有智能建立连接、优化数据传输、进行安全加密、兼容移动网络等特性,使用参考苹果的API说明文档。

AFNetWorking

​ 很好用的第三方框架,在GitHub上可以看到其说明文档。

TTNetworkManager

​ 头条自研的API,暂时没有使用过,主要有如下特性:

  1. 具有AFNetwork和ChromeNet内核,可以动态切换

  2. 有细致的timing信息,可以用来排查网络瓶颈。

  3. 错误信息明确

  4. C++跨平台源码公开

  5. 支持HTTPDNS(传统的DNS需要在运营商进行IP地址转换,HTTP可以在服务器存储的列表中拿到IP地址)

  6. 支持HTTP2.0和QUIC

  7. 支持选路和流控等新功能