底层学习
- 前言
- 基本概念及原理
- NSThread
- GCD
-
- 函数
- 队列
-
-
- [主队列 和 全局并发队列](#主队列 和 全局并发队列)
- 函数与队列的不同组合
-
- [串行队列 + 同步派发](#串行队列 + 同步派发)
- [串行队列 + 异步派发](#串行队列 + 异步派发)
- [并发队列 + 同步派发](#并发队列 + 同步派发)
- [并发队列 + 异步派发](#并发队列 + 异步派发)
- [主队列 + 同步函数](#主队列 + 同步函数)
- [主队列 + 异步派发](#主队列 + 异步派发)
- [全局并发队列 + 同步函数](#全局并发队列 + 同步函数)
- [全局并发队列 + 异步函数](#全局并发队列 + 异步函数)
- 总结
- dispatch_after
- dispatch_once
- dispatch_apply
- dispatch_group_t
-
-
- [dispatch_group_async + dispatch_group_notify](#dispatch_group_async + dispatch_group_notify)
- [dispatch_group_enter + dispatch_group_leave + dispatch_group_notify](#dispatch_group_enter + dispatch_group_leave + dispatch_group_notify)
-
-
- NSOperation
前言
多线程在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...
方法创建,主要是用于获取主线程
,以及后台线程
objective-c
//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]);
}
属性
objective-c
- 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
语言。
日常开发中,一般为以下形式:
objective-c
dispatch_async( dispatch_queue_create("com.CJL.Queue", NULL), ^{
NSLog(@"GCD基本使用");
});
GCD
的核心
主要是由 任务 + 队列 + 函数
构成
objectivec
//********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
表示,这两种均表示默认的串行队列
-
objectivec
// 串行队列的获取方法
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);
创建并发队列 -
注意:并发队列的并发功能只有在
异步函数
下才有效
-
objectivec
// 并发队列的获取方法
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.CJL.Queue", DISPATCH_QUEUE_CONCURRENT);
主队列 和 全局并发队列
-
主队列
(Main Dispatch Queue):GCD中提供的特殊的串行队列-
专门用来
在主线程上调度任务的串行队列
,依赖于主线程、主Runloop,在main函数调用之前自动创建
-
不会开启线程
-
如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度
-
使用
dispatch_get_main_queue()
获得主队列
-
通常在返回
主线程 更新UI
时使用
objectivec//主队列的获取方法 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
- 第一个参数表示
objectivec
//全局并发队列的获取方法
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
全局并发队列 + 主队列 配合使用
在日常开发中,全局队列+并发并列
一般是这样配合使用的
objectivec
//主队列 + 全局并发队列的日常使用
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加入到队列
objectivec
- (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中的代码只执行一次,可以用来创建单例
objectivec
- (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循环)
应用场景:在拉取网络数据后提前计算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性
objectivec
- (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
执行结束之后会受收到通知
objectivec
- (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
成对出现,使进出组的逻辑更加清晰
objectivec
- (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
objectivec
- (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中。
objectivec
//基本使用
- (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
objectivec
//直接处理事务,不添加隐性队列
- (void)cjl_createNSOperation{
//创建NSInvocationOperation对象并关联方法,之后start。
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doSomething:) object:@"CJL"];
[invocationOperation start];
}
- 2、使用子类
NSBlockOperation
objectivec
- (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的子类
,通过实现内部相应的方法来封装任务。
objectivec
//*********自定义继承自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
有两种队列:主队列
、其他队列
。其他队列包含了 串行和并发
。
- 主队列:
主队列
上的任务是在主线程
执行的。 - 其他队列(非主队列):加入到'非队列'中的任务
默认就是并发
,开启多线程。
objectivec
- (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");
}
设置执行顺序
objectivec
//执行顺序
- (void)cjl_testQueueSequence{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{
NSLog(@"%@---%d", [NSThread currentThread], i);
}];
}
}
设置优先级
objectivec
- (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];
}

设置并发数
objectivec
//设置并发数
- (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]);
}];
}
}
添加依赖(先后顺序)
objectivec
//添加依赖
- (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(@"执行完了?我要干其他事");
}
线程间通讯
objectivec
//线程间通讯
- (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]);
}];
}];
}