「iOS」——多线程原理总结
底层学习
- 前言
- 基本概念及原理
- NSThread
- GCD
-
- 函数
- 队列
-
-
- 主队列 和 全局并发队列
- 函数与队列的不同组合
-
- 串行队列 + 同步派发
- 串行队列 + 异步派发
- 并发队列 + 同步派发
- 并发队列 + 异步派发
- 主队列 + 同步函数
- 主队列 + 异步派发
- 全局并发队列 + 同步函数
- 全局并发队列 + 异步函数
- 总结
- dispatch_after
- dispatch_once
- dispatch_apply
- dispatch_group_t
-
-
- dispatch_group_async + dispatch_group_notify
- dispatch_group_enter + dispatch_group_leave + dispatch_group_notify
-
-
- NSOperation
-
- NSOperationQueue
前言
多线程在iOS的开发中起到了非常重要的作用,是IOS开发必须掌握的一环,现在开始进行一个简单的总结。
基本概念及原理
线程、进程与队列
线程的定义:
线程是进程的基本执行单元,一个进程的所有任务都在线程中执行
- 进程想要执行任务,必须得有线程,
进程至少要有一条线程
程序启动会默认开启一条线程
,这条线程被成为主线程
或UI线程
进程的定义:
进程
是指在系统中正在运行的一个应用程序,如微信、支付宝app都是一个进程- 每个
进程
之间是独立的,每个进程均运行在其专用的且受保护的内存空间内
通俗地说,可以理解为:进程是线程的容器,而线程用来执行任务。在iOS
中是单进程开发,一个进程就是一个app
,进程之间是相互独立的,如支付宝、微信、qq等,这些都是属于不同的进程。
线程与进程之间的联系与区别:
- 地址空间:同一
进程
的线程
共享本进程的地址空间,而进程之间则是独立的地址空间 - 资源拥有:同一
进程
内的线程
共享本进程的资源如内存、I/O、cpu等,但是进程
之间的资源是独立的 - 一个
进程
崩溃后,在保护模式下不会对其他进程
产生影响,但是一个线程
崩溃整个进程
都死掉,所以多进程
要比多线程
健壮 进程
切换时,消耗的资源大、效率高.所以设计到频繁的切换时,使用线程
要好于进程
。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程
而不能用进程
线程
是处理器调度的基本单位,但进程
不是- 线程没有地址空间,线程包含在进程地址空间中
线程和runloop的关系
-
runloop与线程是一一对应的
—— 一个runloop
对应一个核心的线程
,为什么说是核心的,是因为runloop
是可以嵌套的,但是核心的只能有一个,他们的关系保存在一个全局的字典里 -
runloop是来管理线程的
—— 当线程的runloop
被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务 -
runloop
在第一次获取时被创建,在线程结束时被销毁-
对于主线程来说,
runloop
在程序一启动就默认创建好了 -
对于子线程来说,
runloop
是懒加载的 —— 只有当我们使用的时候才会创建,所以在子线程用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调
-
影响任务执行速度的因素
以下因素都会对任务的执行速度造成影响:
cpu
的调度- 线程的执行速率
- 队列情况
- 任务执行的复杂度
- 任务的优先级
多线程
多线程生命周期
多线程的生命周期主要分为5部分:新建 - 就绪 - 运行 - 阻塞 - 死亡!
- 新建:实例化线程对象
- 就绪:线程对象调用
start
方法,将线程对象加入可调度线程池
,等待CPU的调用
,即调用start
方法,并不会立即执行,进入就绪状态
,需要等待一段时间,经CPU
调度后才执行,也就是从就绪状态进入运行状态
- 运行:
CPU
负责调度可调度线程池中线程的执行。在线程执行完成之前,其状态可能会在就绪和运行之间来回切换.就绪和运行之间的状态变化由CPU负责,程序员不能干预 - 阻塞:当满足某个预定条件时,可以
使用休眠或锁
,阻塞线程执行。sleepForTimeInterval
(休眠指定时长),sleepUntilDate
(休眠到指定日期),@synchronized(self)
:(互斥锁) - 死亡:正常死亡,即线程执行完毕。非正常死亡,即当满足某个条件后,在线程内部(或者主线程中)终止执行(调用exit方法等退出)
处于运行中的线程
拥有一段可以执行的时间(称为时间片):
- 如果
时间片用尽
,线程就会进入就绪状态队列
- 如果
时间片没有用尽
,且需要开始等待某事件
,就会进入阻塞状态队列
- 等待事件发生后,线程又会重新进入
就绪状态队列
- 每当一个
线程离开运行
,即执行完毕或者强制退出后,会重新从就绪状态队列
中选择一个线程继续执行
关于线程的exit和cancel方法:
exit
:一旦强行终止线程,后续的所有代码都不会执行cancel
:取消当前线程,但是不能取消正在执行的线程
线程池的原理
可以看到主要分为以下四步:
- 判断核心线程池是否都正在执行任务:
- 返回NO,创建新的工作线程去执行
- 返回YES,进行第二步
- 判断线程池工作队列是否已经饱满:
- 返回NO,将任务存储到工作队列,等待CPU调度
- 返回YES,进入第三步
- 判断线程池中的线程是否都处于执行状态
- 返回NO,安排可调度线程池中空闲的线程去执行任务
- 返回YES,进入第四步
- 交给饱和策略去执行,主要有以下四种:
AbortPolicy
:直接抛出RejectedExecutionExeception
异常来阻止系统正常运行CallerRunsPolicy
:将任务回退到调用者DisOldestPolicy
:丢掉等待最久的任务DisCardPolicy
:直接丢弃任务
iOS中多线程的实现方式
iOS中多线程的实现方式主要有四种:pthread、NSThread、GCD、NSOperation
线程安全问题
当多个线程同时访问一块内存,容易引发数据错乱和数据安全问题,有以下两种解决方案:
- 互斥锁(即同步锁):
@synchronized
- 自旋锁
互斥锁
- 保证锁内的代码,同一时间,只有一条线程能够执行!
- 互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差!
- 加了互斥锁的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠
- 能够加锁的是任意 NSObject 对象,但必须是 NSObject 对象
- 锁对象必须保证所有线程都能访问
- 单点加锁时推荐使用 self
自旋锁
-
自旋锁与互斥锁类似,但它不是通过休眠使线程阻塞,而是在获取锁之前一直处于
忙等
(即原地打转,称为自旋)阻塞状态 -
使用场景:锁持有的时间短,且线程不希望在重新调度上花太多成本时,就需要使用自旋锁,属性修饰符
atomic
,本身就有一把自旋锁
-
加入了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用
死循环
的方法,一直等待锁定的代码执行完成,即不停的尝试执行代码,比较消耗性能
、 -
atomic
本身就有一把锁(自旋锁
)
iOS开发的建议:
- 所有属性都声明为
nonatomic
- 尽量避免多线程抢夺同一块资源 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,减小移动客户端的压力
对比GCD和NSOperation
GCD
和NSOperation
的关系如下:
GCD
是面向底层的C
语言的API
NSOperation
是用GCD
封装构建的,是GCD
的高级抽象
GCD和NSOperation的对比如下:
GCD
执行效率更高,而且由于队列中执行的是由block
构成的任务,这是一个轻量级的数据结构 —— 写起来更加方便GCD
只支持FIFO
的队列,而NSOpration
可以设置最大并发数、设置优先级、添加依赖关系等调整执行顺序NSOpration
甚至可以跨队列设置依赖关系,但是GCD
只能通过设置串行队列,或者在队列内添加barrier
任务才能控制执行顺序,较为复杂NSOperation
支持KVO
(面向对象)可以检测operation
是否正在执行、是否结束、是否取消(如果是自定义的NSOperation 子类,需要手动触发KVO通知)
NSThread
NSthread
是苹果官方提供面向对象的线程操作技术,是对thread
的上层封装,比较偏向于底层。
通过NSThread创建线程的方式主要有以下三种方式:
- 通过
init
初始化方式创建 - 通过
detachNewThreadSelector
构造器方式创建 - 通过
performSelector...
方法创建,主要是用于获取主线程
,以及后台线程
//1、创建- (void)cjl_createNSThread{ NSString *threadName1 = @\"NSThread1\"; NSString *threadName2 = @\"NSThread2\"; NSString *threadName3 = @\"NSThread3\"; NSString *threadNameMain = @\"NSThreadMain\"; //方式一:初始化方式,需要手动启动 NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:threadName1]; [thread1 start]; //方式二:构造器方式,自动启动 [NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:threadName2]; //方式三:performSelector...方法创建 [self performSelectorInBackground:@selector(doSomething:) withObject:threadName3]; //方式四 [self performSelectorOnMainThread:@selector(doSomething:) withObject:threadNameMain waitUntilDone:YES]; }- (void)doSomething:(NSObject *)objc{ NSLog(@\"%@ - %@\", objc, [NSThread currentThread]);}
属性
- thread.isExecuting //线程是否在执行- thread.isCancelled //线程是否被取消- thread.isFinished //是否完成- thread.isMainThread //是否是主线程- thread.threadPriority //线程的优先级,取值范围0.0-1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高
NSThread常用的类方法有以下:
currentThread
:获取当前线程sleep...
:阻塞线程exit
:退出线程mainThread
:获取主线程
GCD
GCD就是Grand Central Dispatch
,它是纯 C
语言。
日常开发中,一般为以下形式:
dispatch_async( dispatch_queue_create(\"com.CJL.Queue\", NULL), ^{ NSLog(@\"GCD基本使用\");});
GCD
的核心
主要是由 任务 + 队列 + 函数
构成
//********GCD基础写法********//创建任务dispatch_block_t block = ^{ NSLog(@\"hello GCD\");};//创建串行队列dispatch_queue_t queue = dispatch_queue_create(\"com.CJL.Queue\", NULL);//将任务添加到队列,并指定函数执行dispatch_async(queue, block);
- 使用
dispatch_block_t
创建任务 - 使用
dispatch_queue_t
创建队列 - 将任务添加到队列,并指定执行任务的函数
dispatch_async
注意
这里的任务
是指执行操作
的意思,在使用dispatch_block_t
创建任务时,主要有以下两点说明
- 任务使用
block封装
- 任务的block
没有参数
也没有返回值
函数
在GCD
中执行任务的方式有两种,同步执行
和异步执行
,分别对应同步函数dispatch_sync
和 异步函数dispatch_async
。
- 同步执行,对应同步函数
dispatch_sync
- 必须等待当前语句执行完毕,才会执行下一条语句
不会开启线程
,即不具备开启新线程的能力- 在当前线程中执行
block
任务
- 异步执行,对应异步函数
dispatch_async
- 不用等待当前语句执行完毕,就可以执行下一条语句
会开启线程
执行block
任务,即具备开启新线程的能力(但并不一定开启新线程,这个与任务所指定的队列类型有关)- 异步是多线程的代名词
综上所述,两种执行方式的主要区别
有两点:
是否等待
队列的任务执行完毕是否具备开启新线程
的能力
队列
多线程中所说的队列
(Dispatch Queue
)是指执行任务的等待队列
,即用来存放任务的队列.队列是一种特殊的线性表
,遵循先进先出(FIFO)
原则,即新任务总是被插入到队尾,而任务的读取从队首开始读取.每读取一个任务,则动队列中释放一个任务。而队列又分为串行队列和并发队列
串行队列:每次只有一个任务被执行
,等待上一个任务执行完毕再执行下一个,即只开启一个线程
并发队列:一次可以并发执行多个任务
,即开启多个线程
,并同时执行任务
-
串行队列
:每次只有一个任务被执行
,等待上一个任务执行完毕再执行下一个,即只开启一个线程
(通俗理解:同一时刻只调度一个任务执行)-
使用
dispatch_queue_create(\"xxx\", DISPATCH_QUEUE_SERIAL);
创建串行队列 -
其中的
DISPATCH_QUEUE_SERIAL
也可以使用NULL
表示,这两种均表示默认的串行队列
-
// 串行队列的获取方法dispatch_queue_t serialQueue1 = dispatch_queue_create(\"com.CJL.Queue\", NULL); dispatch_queue_t serialQueue2 = dispatch_queue_create(\"com.CJL.Queue\", DISPATCH_QUEUE_SERIAL);
-
并发队列
:一次可以并发执行多个任务
,即开启多个线程
,并同时执行任务(通俗理解:同一时刻可以调度多个任务执行)-
使用
dispatch_queue_create(\"xxx\", DISPATCH_QUEUE_CONCURRENT);
创建并发队列 -
注意:并发队列的并发功能只有在
异步函数
下才有效
-
// 并发队列的获取方法dispatch_queue_t concurrentQueue = dispatch_queue_create(\"com.CJL.Queue\", DISPATCH_QUEUE_CONCURRENT);
主队列 和 全局并发队列
-
主队列
(Main Dispatch Queue):GCD中提供的特殊的串行队列-
专门用来
在主线程上调度任务的串行队列
,依赖于主线程、主Runloop,在main函数调用之前自动创建
-
不会开启线程
-
如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
-
使用
dispatch_get_main_queue()
获得主队列
-
通常在返回
主线程 更新UI
时使用
//主队列的获取方法dispatch_queue_t mainQueue = dispatch_get_main_queue();
-
-
全局并发队列
(Global Dispatch Queue):GCD提供的默认的并发队列- 为了方便程序员的使用,苹果提供了全局队列
- 在使用多线程开发时,如果对队列没有特殊需求,
在执行异步任务时,可以直接使用全局队列
- 使用
dispatch_get_global_queue
获取全局并发队列,最简单的是dispatch_get_global_queue(0, 0)
- 第一个参数表示
队列优先级
,默认优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT=0
,在ios9之后,已经被服务质量(quality-of-service)
取代 - 第二个参数使用0
- 第一个参数表示
//全局并发队列的获取方法 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); //优先级从高到低(对应的服务质量)依次为 - DISPATCH_QUEUE_PRIORITY_HIGH -- QOS_CLASS_USER_INITIATED - DISPATCH_QUEUE_PRIORITY_DEFAULT -- QOS_CLASS_DEFAULT - DISPATCH_QUEUE_PRIORITY_LOW -- QOS_CLASS_UTILITY - DISPATCH_QUEUE_PRIORITY_BACKGROUND -- QOS_CLASS_BACKGROUND
全局并发队列 + 主队列 配合使用
在日常开发中,全局队列+并发并列
一般是这样配合使用的
//主队列 + 全局并发队列的日常使用 dispatch_async(dispatch_get_global_queue(0, 0), ^{ //执行耗时操作 dispatch_async(dispatch_get_main_queue(), ^{ //回到主线程进行UI操作 }); });
函数与队列的不同组合
串行队列 + 同步派发
任务一个接一个地在当前线程执行,不会开辟新线程
串行队列 + 异步派发
任务一个接一个地执行,但是会开辟新线程
并发队列 + 同步派发
任务一个接一个地执行,不开辟线程
并发队列 + 异步派发
任务乱序进行并且会开辟新线程
主队列 + 同步函数
任务互相等待,造成死锁
·
为什么这样会造成死锁,这里分析一下原因:
-
主队列有两个任务,顺序为:
NSLog任务 - 同步block
-
执行NSLog任务后,执行同步Block,会将任务1(即i=1时)加入到主队列,主队列顺序为:
NSLog任务 - 同步block - 任务1
-
任务1
的执行需要
等待同步block执行完毕才会执行,而
同步block的执行需要
等待任务1执行完毕,所以就造成了
任务互相等待的情况,即造成
死锁崩溃
死锁:
主线程
因为同步函数
的原因等着先执行任务主队列
等着主线程的任务执行完毕再执行自己的任务主队列和主线程
相互等待会造成死锁
主队列 + 异步派发
主队列是一个特殊的串行队列,它虽然是串行队列,但是其异步派发不会开辟新线程,而是将任务安排到主线程的下一个运行循环(Run Loop)周期执行
全局并发队列 + 同步函数
【任务按顺序执行
】:任务一个接一个的执行,不开辟新线程
全局并发队列 + 异步函数
【任务乱序执行
】:任务乱序执行,会开辟新线程
总结
dispatch_after
dispatch_after表示在队列中的block延迟执行,确切地说是延迟将block加入到队列
- (void)cjl_testAfter{ /* dispatch_after表示在某队列中的block延迟执行 应用场景:在主队列上延迟执行一项任务,如viewDidload之后延迟1s,提示一个alertview(是延迟加入到队列,而不是延迟执行) */ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@\"2s后输出\"); }); }
dispatch_once
dispatch_once可以保证在app运行期间,block中的代码只执行一次,可以用来创建单例
- (void)cjl_testOnce{ /* dispatch_once保证在App运行期间,block中的代码只执行一次 应用场景:单例、method-Swizzling */ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //创建单例、method swizzled或其他任务 NSLog(@\"创建单例\"); });}
dispatch_apply
dispatch_apply将指定的block追加到指定的队列中重复执行,并等到全部的处理执行结束(相当于线程安全的for循环)
应用场景:在拉取网络数据后提前计算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性
- (void)cjl_testApply{ /* dispatch_apply将指定的Block追加到指定的队列中重复执行,并等到全部的处理执行结束——相当于线程安全的for循环 应用场景:用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性 - 添加到串行队列中——按序执行 - 添加到主队列中——死锁 - 添加到并发队列中——乱序执行 - 添加到全局队列中——乱序执行 */ dispatch_queue_t queue = dispatch_queue_create(\"CJL\", DISPATCH_QUEUE_SERIAL); NSLog(@\"dispatch_apply前\"); /** param1:重复次数 param2:追加的队列 param3:执行任务 */ dispatch_apply(10, queue, ^(size_t index) { NSLog(@\"dispatch_apply 的线程 %zu - %@\", index, [NSThread currentThread]); }); NSLog(@\"dispatch_apply后\");}
dispatch_group_t
dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间
dispatch_group_notify
- 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
dispatch_group_wait
- 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
dispatch_group_enter、dispatch_group_leave
-
dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
-
dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
-
当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait 解除阻塞,以及执行追加到 dispatch_group_notify 中的任务。
应用场景:多个接口请求之后刷新页面
dispatch_group_async + dispatch_group_notify
dispatch_group_notify
在dispatch_group_async
执行结束之后会受收到通知
- (void)cjl_testGroup1{ /* dispatch_group_t:调度组将任务分组执行,能监听任务组完成,并设置等待时间 应用场景:多个接口请求之后刷新页面 */ dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_async(group, queue, ^{ NSLog(@\"请求一完成\"); }); dispatch_group_async(group, queue, ^{ NSLog(@\"请求二完成\"); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@\"刷新页面\"); }); //或者用下面这个:// 等待所有任务完成dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC));if (dispatch_group_wait(group, timeout) == 0) { // 所有数据获取完成,进行最终处理 NSLog(@\"All data fetched!\");} else { // 超时,可以执行一些错误处理逻辑 NSLog(@\"Timeout waiting for data\");}}
与 dispatch_group_notify
不同,dispatch_group_wait
是一个同步操作,会阻塞当前线程,直到所有任务完成或者超时。在这里,我们设置了一个 10 秒的超时时间,如果在 10 秒内所有任务都完成,则打印 “All data fetched!”。如果超时,则打印 “Timeout waiting for data”。
dispatch_group_enter + dispatch_group_leave + dispatch_group_notify
dispatch_group_enter
和dispatch_group_leave
成对出现,使进出组的逻辑更加清晰
- (void)cjl_testGroup2{ /* dispatch_group_enter和dispatch_group_leave成对出现,使进出组的逻辑更加清晰 */ dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_enter(group); dispatch_async(queue, ^{ NSLog(@\"请求一完成\"); dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(queue, ^{ NSLog(@\"请求二完成\"); dispatch_group_leave(group); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@\"刷新界面\"); });}
在此基础上还可以使用 dispatch_group_wait
- (void)cjl_testGroup3{ /* long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout) group:需要等待的调度组 timeout:等待的超时时间(即等多久) - 设置为DISPATCH_TIME_NOW意味着不等待直接判定调度组是否执行完毕 - 设置为DISPATCH_TIME_FOREVER则会阻塞当前调度组,直到调度组执行完毕 返回值:为long类型 - 返回值为0——在指定时间内调度组完成了任务 - 返回值不为0——在指定时间内调度组没有按时完成任务 */ dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_group_enter(group); dispatch_async(queue, ^{ NSLog(@\"请求一完成\"); dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(queue, ^{ NSLog(@\"请求二完成\"); dispatch_group_leave(group); }); // long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);// long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER); long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC)); NSLog(@\"timeout = %ld\", timeout); if (timeout == 0) { NSLog(@\"按时完成任务\"); }else{ NSLog(@\"超时\"); } dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@\"刷新界面\"); });}
NSOperation
NSOperation
是基于GCD之上的更高一层封装,NSOperation需要配合NSOperationQueue
来实现多线程
。
NSOperatino实现多线程的步骤如下:
- 1、
创建任务
:先将需要执行的操作封装到NSOperation对象中。 - 2、
创建队列
:创建NSOperationQueue。 - 3、
将任务加入到队列中
:将NSOperation对象添加到NSOperationQueue中。
//基本使用- (void)cjl_testBaseNSOperation{ //处理事务 NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation::) object:@\"CJL\"]; //创建队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; //操作加入队列 [queue addOperation:op]; }- (void)handleInvocation:(id)operation{ NSLog(@\"%@ - %@\", operation, [NSThread currentThread]);}
需要注意的是,NSOperation是个抽象类,实际运用时中需要使用它的子类,有三种方式:
- 1、使用子类
NSInvocationOperation
//直接处理事务,不添加隐性队列- (void)cjl_createNSOperation{ //创建NSInvocationOperation对象并关联方法,之后start。 NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@\"CJL\"]; [invocationOperation start];}
- 2、使用子类
NSBlockOperation
- (void)cjl_testNSBlockOperationExecution{ //通过addExecutionBlock这个方法可以让NSBlockOperation实现多线程。 //NSBlockOperation创建时block中的任务是在主线程执行,而运用addExecutionBlock加入的任务是在子线程执行的。 NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@\"main task = >currentThread: %@\", [NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@\"task1 = >currentThread: %@\", [NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@\"task2 = >currentThread: %@\", [NSThread currentThread]); }]; [blockOperation addExecutionBlock:^{ NSLog(@\"task3 = >currentThread: %@\", [NSThread currentThread]); }]; [blockOperation start];}
- 3、定义继承自
NSOperation的子类
,通过实现内部相应的方法来封装任务。
//*********自定义继承自NSOperation的子类*********@interface CJLOperation : NSOperation@end@implementation CJLOperation- (void)main{ for (int i = 0; i < 3; i++) { NSLog(@\"NSOperation的子类:%@\",[NSThread currentThread]); }}@end//*********使用*********- (void)cjl_testCJLOperation{ //运用继承自NSOperation的子类 首先我们定义一个继承自NSOperation的类,然后重写它的main方法。 CJLOperation *operation = [[CJLOperation alloc] init]; [operation start];}
NSOperationQueue
NSOperationQueue添加事务
NSOperationQueue
有两种队列:主队列
、其他队列
。其他队列包含了 串行和并发
。
- 主队列:
主队列
上的任务是在主线程
执行的。 - 其他队列(非主队列):加入到’非队列’中的任务
默认就是并发
,开启多线程。
- (void)cjl_testNSOperationQueue{ /* NSInvocationOperation和NSBlockOperation两者的区别在于: - 前者类似target形式 - 后者类似block形式——函数式编程,业务逻辑代码可读性更高 NSOperationQueue是异步执行的,所以任务一、任务二的完成顺序不确定 */ // 初始化添加事务 NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@\"任务1————%@\",[NSThread currentThread]); }]; // 添加事务 [bo addExecutionBlock:^{ NSLog(@\"任务2————%@\",[NSThread currentThread]); }]; // 回调监听 bo.completionBlock = ^{ NSLog(@\"完成了!!!\"); }; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:bo]; NSLog(@\"事务添加进了NSOperationQueue\");}
设置执行顺序
//执行顺序- (void)cjl_testQueueSequence{ NSOperationQueue *queue = [[NSOperationQueue alloc] init]; for (int i = 0; i < 5; i++) { [queue addOperationWithBlock:^{ NSLog(@\"%@---%d\", [NSThread currentThread], i); }]; }}
设置优先级
- (void)cjl_testOperationQuality{ /* NSOperation设置优先级只会让CPU有更高的几率调用,不是说设置高就一定全部先完成 - 不使用sleep——高优先级的任务一先于低优先级的任务二 - 使用sleep进行延时——高优先级的任务一慢于低优先级的任务二 */ NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 5; i++) { //sleep(1); NSLog(@\"第一个操作 %d --- %@\", i, [NSThread currentThread]); } }]; // 设置最高优先级 bo1.qualityOfService = NSQualityOfServiceUserInteractive; NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 5; i++) { NSLog(@\"第二个操作 %d --- %@\", i, [NSThread currentThread]); } }]; // 设置最低优先级 bo2.qualityOfService = NSQualityOfServiceBackground; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:bo1]; [queue addOperation:bo2];}
设置并发数
//设置并发数- (void)cjl_testOperationMaxCount{ /* 在GCD中只能使用信号量来设置并发数 而NSOperation轻易就能设置并发数 通过设置maxConcurrentOperationCount来控制单次出队列去执行的任务数 */ NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.name = @\"Felix\"; queue.maxConcurrentOperationCount = 2; for (int i = 0; i < 5; i++) { [queue addOperationWithBlock:^{ // 一个任务 [NSThread sleepForTimeInterval:2]; NSLog(@\"%d-%@\",i,[NSThread currentThread]); }]; }}
添加依赖(先后顺序)
//添加依赖- (void)cjl_testOperationDependency{ NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:0.5]; NSLog(@\"请求token\"); }]; NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:0.5]; NSLog(@\"拿着token,请求数据1\"); }]; NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:0.5]; NSLog(@\"拿着数据1,请求数据2\"); }]; [bo2 addDependency:bo1]; [bo3 addDependency:bo2]; [queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES]; NSLog(@\"执行完了?我要干其他事\");}
线程间通讯
//线程间通讯- (void)cjl_testOperationNoti{ NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.name = @\"Felix\"; [queue addOperationWithBlock:^{ NSLog(@\"请求网络%@--%@\", [NSOperationQueue currentQueue], [NSThread currentThread]); [[NSOperationQueue mainQueue] addOperationWithBlock:^{ NSLog(@\"刷新UI%@--%@\", [NSOperationQueue currentQueue], [NSThread currentThread]); }]; }];}