简单的整理了一下,多线程的创建方式,它们之间的优缺点,以及在项目中我们在什么样的场景下选择哪一种方式。水平有限,写得比较浅显易懂,太深入的可以自己去学习、去钻研,没有什么是学不会的,前提只要你肯学。废话不多说,let's go!!!
创建线程的的方案有pthread,NSThread,GCD,NSOperation,那么我就依次说一下每种方案有什么优缺点,及它们是怎样创建线程的。
1.0 pthread
a. 简介
pthread(POSIX thread)表示跨平台的的线程接口,适用于Unix\Linux\Windows等系统,是跨平台和可移植的,使用的语言是C语言,线程的生命周期是由程序员管理的,适用难度很大,所以几乎是不用的。
b. 创建子线程
所用函数:
int pthread_create(pthread_t * __restrict, const pthread_attr_t * __restrict,
void *(*)(void *), void * __restrict);
参数:
参数1:线程的编号(地址)
参数2:线程属性,传入指向线程属性的指针地址。
参数3:新线程要执行的函数(任务),传入函数地址,即函数名。
参数4: 给调用用的函数传递的参数。
返回值:
返回int类型的值,0表示创建新线程成功,反之,创建新线程失败,返回失败的编号。
很多C语言框架里面并不是非零即真原则;因为他们认为成功的结果只有一个,但是失败的原因有很多。
demo
- (void)pthreadDemo {
pthread_t ID;
int returnValue = pthread_create( &ID, NULL, pthreadAction, NULL);
if (returnValue == 0) {
NSLog(@"线程创建成功");
} else {
NSLog(@"线程创建失败");
}
}
void *pthreadAction(void *param) {
NSLog(@"%@",[NSThread currentThread]);
return NULL;
}
打印结果
2018-10-18 11:09:41.738469+0800 多线程Demo[8228:2385086] 线程创建成功
2018-10-18 11:09:41.738770+0800 多线程Demo[8228:2385731] <NSThread: 0x600000268840>{number = 3, name = (null)}
2018-10-18 11:09:41.903999+0800 多线程Demo[8228:2385086] 线程创建成功
2018-10-18 11:09:41.904245+0800 多线程Demo[8228:2385735] <NSThread: 0x600000260bc0>{number = 4, name = (null)}
从上述结果看出,创建新线程成功。
c.还有一点我们需要注意的是__bridge使用,例:
NSString *ocString = @"tianyao";
int returnValue = pthread_create( &ID, NULL, pthreadAction, (__bridge void *)(ocString));
在混合开发的时候,在C和OC或者swift之间传递数据,需要使用__bridge进行桥接,它的目的就是告诉编译器怎样管理内存。因为在ARC开发模式下,OC和swift编译器在编译的时候,根据代码的结构,自动的添加retain/release/autorelease。但是,ARC 只负责管理 OC 部分的内存管理,而不负责C语言代码的内存管理。因此,如果使用的 C 语言框架出现 retain/create/copy/new 等字样的函数,大多都需要 release,否则会出现内存泄漏。
像上述代码,在c的函数里面传递OC的字符串,就需要用到__bridge,因为c的内存需要手动释放,而OC的出了作用域就会自动释放,这样就会出问题,所以使用__bridge就是告诉编译器C函数中的OC代码的内存管理交给C处理了,你不用管了。
2.0 NSThread
a. 简介
NSThread的使用更加面向对象,简单易用,可以直接操作线程对象,使用的语言是OC,但是线程的生命周期是由程序员自行管理的,所以偶尔会使用。
b. 创建线程的三种方式
- 对象方法创建
手动开启线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"tianyao"]
// 手动开启线程
[thread start]
- 类方法创建
自动开启线程,这样的话就无法获取线程对象
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"tianyao"];
- NSObject(NSThreadPerformAdditions) 的分类创建
方便任何继承自NSObject的对象,都可以很容易的调用线程方法,也是无法获取线程对象
[self performSelectorInBackground:@selector(demo:) withObject:@"tianyao"];
c. 线程属性
- name: 线程名称
当线程执行的方法的内部出现异常时,可以记录当前和异常的线程。 - stackSize:栈区大小
可以通过[NSThread currentThread].stackSize = 512 * 1024设置栈区的大小(必须是4KB的倍数)。 - isMainThread: 是否主线程
- threadPriority: 线程优先级
它是一个浮点数,范围是0~1.0,1.0表示最高优先级,0.0表示最低优先级,系统默认的优先级是0.5,线程的优先级并不能绝对的保证优先级高的线程执行完所有的代码,它只是提高了优先级高的代码执行代码的概率。 - qualityOfService:服务质量(IOS8推出)
NSQualityOfServiceUserInteractive - 用户交互,例如绘图或者处理用户事件
NSQualityOfServiceUserInitiated - 用户需要
NSQualityOfServiceUtility - 实用工具,用户不需要立即得到结果
NSQualityOfServiceBackground - 后台
NSQualityOfServiceDefault - 默认,介于用户需要和实用工具之间
注意
开发中最好不好修改优先级,不要相信用户交互服务质量
3.0 GCD
a. 简介(IOS4.0后推出)
GCD(Grand Central Dispatch)是苹果公司为多核的并行运算提出的解决方案,会自动利用更多的CPU内核(比如双核、四核),是纯C语言的,提供了很多强大的函数,能够自动的管理线程的生命周期(创建线程、调度任务、销毁线程),在平时的工作中经常使用。
b. GCD核心
GCD的核心就是将任务添加到队列中。
- 队列
串行队列:任务按顺序有序的执行。就像单行车道,车只能一辆一辆的按照顺序行驶
并发队列:可以让多个任务并发执行。那么并发队列就可类比于多行车道。 - 任务
任务的执行可分为两种方式
同步执行:在当前线程中依次执行任务。
异步执行:创建一个新线程来执行任务。
c. 串行队列
串行同步:
- (void)serialSyncDemo {
// 串行队列
/*
参数1:队列名称
参数2:队列类型
*/
dispatch_queue_t serialQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_SERIAL);
for(int i = 0; i < 5; i++){
dispatch_sync(serialQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印结果:
2018-10-18 15:58:53.085081+0800 多线程Demo[8994:2663401] 0 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.085400+0800 多线程Demo[8994:2663401] 1 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.086193+0800 多线程Demo[8994:2663401] 2 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.086402+0800 多线程Demo[8994:2663401] 3 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.086700+0800 多线程Demo[8994:2663401] 4 <NSThread: 0x60400006cd80>{number = 1, name = main}
得出结论:串行同步不具备开启新线程的能力,任务按照顺序执行。
串行异步:
- (void)serialAsyncDemo {
dispatch_queue_t serialQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 5; i++){
dispatch_async(serialQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印结果:
2018-10-18 16:13:58.676331+0800 多线程Demo[9064:2685387] 0 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.678881+0800 多线程Demo[9064:2685387] 1 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.679375+0800 多线程Demo[9064:2685387] 2 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.679847+0800 多线程Demo[9064:2685387] 3 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.680204+0800 多线程Demo[9064:2685387] 4 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
得出结论:串行异步会开启一条子线程,任务一次执行。
d. 并行队列
并行同步:
- (void)concurrentSyncDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 5; i++){
dispatch_sync(concurrentQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印结果:
2018-10-18 16:43:00.811969+0800 多线程Demo[9161:2713449] 0 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.812157+0800 多线程Demo[9161:2713449] 1 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.812328+0800 多线程Demo[9161:2713449] 2 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.812867+0800 多线程Demo[9161:2713449] 3 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.813012+0800 多线程Demo[9161:2713449] 4 <NSThread: 0x60000007fac0>{number = 1, name = main}
得出结论:并行同步不会开启新线程,任务在当前线程依次执行。
并行异步:
- (void)concurrentAsyncDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 5; i++){
dispatch_async(concurrentQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印结果:
2018-10-18 17:02:30.044475+0800 多线程Demo[9208:2730824] 2 <NSThread: 0x604000462380>{number = 4, name = (null)}
2018-10-18 17:02:30.044706+0800 多线程Demo[9208:2730823] 1 <NSThread: 0x604000462000>{number = 5, name = (null)}
2018-10-18 17:02:30.044829+0800 多线程Demo[9208:2730825] 0 <NSThread: 0x60000027c440>{number = 3, name = (null)}
2018-10-18 17:02:30.045005+0800 多线程Demo[9208:2730822] 3 <NSThread: 0x60000027f140>{number = 6, name = (null)}
2018-10-18 17:02:30.045009+0800 多线程Demo[9208:2730834] 4 <NSThread: 0x60000027f400>{number = 7, name = (null)}
得出结论:并行异步会开启多条线程,任务不按顺序执行。
e. 主队列
主队列是专门在主线程上面调度任务的队列,会随着程序启动一起创建,不会开启新的线程,以先进先出的方式执行任务
主队列只有当主线程空闲的时候才会调度任务,也就是说当主线程有任务正在执行时,无论主队列被添加了任何任务都不会被执行。
主队列同步
- (void)mainSyncDemo {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"start");
dispatch_sync(mainQueue, ^{
NSLog(@"%@", [NSThread currentThread]);
});
NSLog(@"end");
}
执行上面的代码会造成死锁,程序崩溃。因为主队列上面的代码只有当主线程空闲的时候才会执行,又因为是同步,代码按顺序执行,当主线程执行到主队列的代码的时候,主线程此时不是处于空闲的状态,所以没法执行主队列的代码,这就造成了主队列和主线程之间的相互等待,造成死锁。
解决死锁的办法: 就是将主队列的代码放到子线程中,不让其阻碍主线程的执行,这样等主线程空闲下来的时候,就可以去执行主队列上面的代码。
- (void)mainSyncDemo {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"start");
dispatch_async(dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT), ^{
dispatch_sync(mainQueue, ^{
NSLog(@"%@", [NSThread currentThread]);
});
});
NSLog(@"end");
}
打印结果:
2018-10-18 18:03:12.375942+0800 多线程Demo[9377:2796585] start
2018-10-18 18:03:12.376192+0800 多线程Demo[9377:2796585] end
2018-10-18 18:03:12.376542+0800 多线程Demo[9377:2796585] <NSThread: 0x60400006acc0>{number = 1, name = main}
主队列异步
- (void)mainAsyncDemo {
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"start");
dispatch_async(mainQueue, ^{
NSLog(@"%@", [NSThread currentThread]);
});
NSLog(@"end");
}
打印结果:
2018-10-18 18:10:35.030586+0800 多线程Demo[9435:2808199] start
2018-10-18 18:10:35.030802+0800 多线程Demo[9435:2808199] end
2018-10-18 18:10:35.031140+0800 多线程Demo[9435:2808199] <NSThread: 0x60000006ac40>{number = 1, name = main}
得出结论:主线程异步不会创建新的线程,任务在主线程上面依次执行。
f. 全局队列
全局队列又叫全局并发队列,是系统为了方便程序员开发提供的,其工作状态与并发队列一致,无论 MRC & ARC 都不需要考虑释放。
全局队列同步:
- (void)globalSync {
/*
参数1:A quality of service 服务质量
iOS 8.0及以后
QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的)
QOS_CLASS_UTILITY 0x11, 实用工具(专门用来处理耗时操作!)
QOS_CLASS_BACKGROUND 0x09, 后台
QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 适配
iOS 7.0及以前
DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
参数2:Reserved for future use 未来使用:为未来保留使用的,应该永远传入0
*/
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
for(int i = 0; i < 5; i++){
dispatch_sync(globalQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印结果:
2018-10-19 10:56:41.119513+0800 多线程Demo[10930:3237013] 0 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.119773+0800 多线程Demo[10930:3237013] 1 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.120436+0800 多线程Demo[10930:3237013] 2 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.120701+0800 多线程Demo[10930:3237013] 3 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.120832+0800 多线程Demo[10930:3237013] 4 <NSThread: 0x600000064440>{number = 1, name = main}
得出结论:全局同步不会创建新的现场,程序在当前现场按顺序执行。
全局队列异步:
- (void)globalAsync {
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
for(int i = 0; i < 5; i++){
dispatch_async(globalQueue, ^{
NSLog(@"%d %@", i, [NSThread currentThread]);
});
}
}
打印结果:
2018-10-19 10:58:54.635833+0800 多线程Demo[10964:3242388] 1 <NSThread: 0x604000460340>{number = 4, name = (null)}
2018-10-19 10:58:54.635987+0800 多线程Demo[10964:3242022] 0 <NSThread: 0x604000274f80>{number = 3, name = (null)}
2018-10-19 10:58:54.636380+0800 多线程Demo[10964:3242393] 2 <NSThread: 0x600000464b40>{number = 5, name = (null)}
2018-10-19 10:58:54.636981+0800 多线程Demo[10964:3242394] 3 <NSThread: 0x600000464bc0>{number = 7, name = (null)}
2018-10-19 10:58:54.637505+0800 多线程Demo[10964:3242395] 4 <NSThread: 0x60400027dc00>{number = 6, name = (null)}
得出结论:全局异步会创建多条线程,任务不按顺序执行。
g. GCD阻塞(Barrier)
应用场景:主要用于多个异步操作完成之后,统一对非线程安全的对象(例如:NSMutableArray,NSMutableDictionary等)做处理。适用于大规模数据的I/O操作。举例说明:
NSMutableArray *_imageArr = [NSMutableArray array];
- (void)barrierDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 2500; i++){
dispatch_async(concurrentQueue, ^{
NSString *name = [NSString stringWithFormat:@"%02d.jpg",i%10 + 1];
NSURL *url = [[NSBundle mainBundle]URLForResource:name withExtension:nil];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [UIImage imageWithData:data];
NSLog(@"%@ %@",name,[NSThread currentThread]);
[self.imageArr addObject:img];
});
}
执行上述代码程序会崩溃,原因是NSMutableArray是非线程安全的,如果出现两个线程同时向数组中添加对象,程序就会崩溃。解决的方案如下:
- (void)barrierDemo {
dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0; i < 2500; i++){
dispatch_async(concurrentQueue, ^{
NSString *name = [NSString stringWithFormat:@"%02d.jpg",i%10 + 1];
NSURL *url = [[NSBundle mainBundle]URLForResource:name withExtension:nil];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [UIImage imageWithData:data];
NSLog(@"%@ %@",name,[NSThread currentThread]);
dispatch_barrier_async(concurrentQueue, ^{
[self.imageArr addObject:img];
});
});
}
}
dispatch_barrier_async可以保证同一时间内只有一条线程执行block内的代码,也就是说在其之前添加的block全部执行完毕之后,才在同一个线程顺序执行,从而保证了非线程安全的对象的正确操作。
f. GCD延迟操作
dispatch_after这个函数默认是异步执行的。
- (void)afterDemo {
NSLog(@"start");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 执行的任务
});
NSLog(@"end");
// 拆分
/*
参数1 : dispatch_time_t when,表示延迟的时间
参数2 : dispatch_queue_t queue,表示任务执行的队列
参数3 : dispatch_block_t block,表示线程要执行的任务
*/
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_block_t block = ^ {
// 执行的任务
};
dispatch_after(time, queue, block);
}
上述代码dispatch_after里面的代码延迟一秒执行。
g. GCD一次执行
GCD的一次执行运用最多的场景就是创建单例,核心就是dispatch_once_t,它的里面有一把锁,能够保证线程的安全。
苹果公司推荐使用
单例:
一个应用程序里面有且只有一个这样的实例对象,例如:网络请求,音乐播放器等。单例一旦创建就会一直存在,直到app退出,单例存在静态区,所以不能滥用。
向我们应用程序中经常使用的好多,也都是单例。例如:
[NSNotificationCenter defaultCenter];
[NSUserDefaults standardUserDefaults];
[UIApplication sharedApplication];
[NSFileManager defaultManager];
如果不使用GCD的话,也可以这样创建单例,使用互斥锁
+ (instancetype)sharedTool {
// 添加互斥锁
@synchronized (self) {
if (instance == nil) {
instance = [[self alloc] init];
}
}
return instance;
}
开发中一般不使用这种方式,因为互斥锁使用的是线程同步的原理,线程之间需要等待,相比dispatch_once效率不高。
懒汉式和饿汉式单例
static id instance;
// 懒汉式单例:使用时才会创建
+ (instancetype)sharedTool {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
// 饿汉式单例:重写initialize这个类方法,在类第一次使用的时候就会创建
// initialize会在类第一次被使用时调用, 且调用是线程安全的
+ (void)initialize {
instance = [[self alloc] init];
}
+ (instancetype)sharedTool {
return instance;
}
有时候我们会看到有些代码会重写allocWithZone和copyWithZone。例如:
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
因为是担心合作开发中,别人会使用TYOnce *tools = [[TYOnce alloc] init]; [tools copy]这种方式来创建单例,但是一般正常的程序员都知道我们创建单例的方式都是使用TYOnce *tools = [TYOnce sharedTool];这种方式,所以这种顾虑基本上可以不用考虑。
h. 调度组
监听一组异步任务是否执行结束,在其执行结束后得到统一的通知。例如:监听几部电影同时下载:
- (void)groupDemo {
//调度组
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"下载第1部电影 %@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"下载第2部电影 %@",[NSThread currentThread]);
});
dispatch_group_async(group, queue, ^{
NSLog(@"下载第3部电影 %@",[NSThread currentThread]);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"电影全部下载完成");
});
}
打印结果:
2018-10-19 15:20:07.473239+0800 05-调度组[11616:3513382] 下载第1首歌曲 <NSThread: 0x604000278b80>{number = 3, name = (null)}
2018-10-19 15:20:07.473239+0800 05-调度组[11616:3513384] 下载第3首歌曲 <NSThread: 0x60400027a980>{number = 4, name = (null)}
2018-10-19 15:20:07.474253+0800 05-调度组[11616:3513381] 下载第2首歌曲 <NSThread: 0x60400027be00>{number = 5, name = (null)}
2018-10-19 15:20:07.474631+0800 05-调度组[11616:3513290] 歌曲下载完成了
由打印的结果可以看出,任务是异步执行的,在三部电影全部下载完成后,可以得到统一的通知。
调度组的执行原理:实现监听一组异步任务是否执行结束
/*
enter等于eave : 监测成功
enter多于leave : 监测失效
enter小于leave : 程序崩溃
*/
- (void)groupDemo {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"下载第1部电影 %@",[NSThread currentThread]);
[NSThread sleepForTimeInterval:2.0];
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"下载第2部电影 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"下载第3部电影 %@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"电影全部下载完成");
});
}
4.0 NSOperation
a. 简介(IOS2.0后推出)
NSOperation是OC语言中基于GCD的面向对象的封装,使用起来比GCD更加简单(面向对象),提供了一些用GCD不好实现的功能(例如:添加依赖),能够自动的管理线程的生命周期,在平时的工作中经常使用。
苹果推荐使用
NSOperation是一个抽象类,所以无法直接使用,因为它的方法只有声明没有实现。核心就是将操作添加到队列当中。
使用时其实我们是对NSOperation子类的使用,它的子类:
NSInvocationOperation;
NSBlockOperation;
// 自定义operation
NSOperation;
b. NSInvocationOperation的使用
- 试例一
- (void)operationDemo {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
[op start];
}
- (void)demo {
NSLog(@"%@", [NSThread currentThread]);
}
打印结果:
2018-10-19 16:25:58.252280+0800 多线程Demo[11856:3583208] <NSThread: 0x604000078680>{number = 1, name = main}
得出结论: [op start]方法,会在当前线程执行selector方法。
- 试例二
- (void)operationDemo {
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo) object:nil];
// 队列: 并发队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 把操作添加到队列
[queue addOperation:op];
}
- (void)demo {
NSLog(@"%@", [NSThread currentThread]);
}
打印结果:
2018-10-19 16:32:03.564602+0800 多线程Demo[11895:3592479] <NSThread: 0x60400026dc00>{number = 3, name = (null)}
得出结论:将操作添加到队列,默认是异步执行。
- 试例三:验证队列的并发性
- (void)operationDemo {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
for(int i = 0;i< 5;i++){
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo) object:nil];
[queue addOperation:op];
}
}
- (void)demo {
NSLog(@"%@", [NSThread currentThread]);
}
打印结果:
2018-10-19 16:36:15.437686+0800 多线程Demo[11936:3600229] <NSThread: 0x60000026c7c0>{number = 6, name = (null)}
2018-10-19 16:36:15.437641+0800 多线程Demo[11936:3600228] <NSThread: 0x604000276c80>{number = 4, name = (null)}
2018-10-19 16:36:15.437744+0800 多线程Demo[11936:3600234] <NSThread: 0x60000026c400>{number = 3, name = (null)}
2018-10-19 16:36:15.437808+0800 多线程Demo[11936:3600232] <NSThread: 0x60000026c780>{number = 5, name = (null)}
2018-10-19 16:36:15.438371+0800 多线程Demo[11936:3600308] <NSThread: 0x604000279840>{number = 7, name = (null)}
得出结论:会开启多条线程,不是顺序执行的。与GCD中并发异步执行效果一样。
c. NSBlockOperation的使用
- 试例一
- (void)blockDemo {
// 封装NSOperation对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[op start];
}
得出结论:
2018-10-19 17:12:50.353660+0800 多线程Demo[12060:3633014] <NSThread: 0x600000067e80>{number = 1, name = main}
得出结论: [op start]方法,操作只在当前线程执行。
- 试例二
- (void)blockDemo {
// 队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
// 封装NSOperation对象
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
// 把操作添加到队列
[queue addOperation:op];
}
打印结果:
2018-10-19 17:15:00.513886+0800 多线程Demo[12079:3637107] <NSThread: 0x60400027e5c0>{number = 3, name = (null)}
得出结论:将操作添加到队列,操作默认是异步的。
- 试例三
- (void)blockDemo {
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
for(int i = 0;i< 5;i++){
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[queue addOperation:op];
}
}
打印结果:
2018-10-19 17:19:32.740483+0800 多线程Demo[12120:3644204] <NSThread: 0x6000002698c0>{number = 5, name = (null)}
2018-10-19 17:19:32.740487+0800 多线程Demo[12120:3644205] <NSThread: 0x604000269fc0>{number = 6, name = (null)}
2018-10-19 17:19:32.740483+0800 多线程Demo[12120:3644203] <NSThread: 0x60400026b800>{number = 4, name = (null)}
2018-10-19 17:19:32.740524+0800 多线程Demo[12120:3644201] <NSThread: 0x600000269d00>{number = 3, name = (null)}
2018-10-19 17:19:32.740646+0800 多线程Demo[12120:3644202] <NSThread: 0x604000269f80>{number = 7, name = (null)}
得出结论:队列默认是并发性的。
- 试例四
在实际开发时,如果要使用到NSOperationQueue,可以直接定义成全局的队列
// 全局队列
@property (nonatomic, strong) NSOperationQueue *queue;
- (NSOperationQueue *)queue {
if (_queue == nil) {
_queue = [[NSOperationQueue alloc]init];
}
return _queue;
}
- (void) blockDemo {
[self.queue addOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
}
- 试例五
添加操作服务质量和监听操作执行结束
操作服务质量:解决队列里面的操作有更多的机会被队列调度执行,类似于线程优先级。
监听操作执行结束:这个监听的回调是异步的。
- (void)OperationDemo {
// 操作1
NSBlockOperation *firstOp = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10; i++) {
NSLog(@"firstOp == %d %@",i,[NSThread currentThread]);
}
}];
// 监听操作1什么时候执行结束 : 这个监听是异步监听
[firstOp setCompletionBlock:^{
NSLog(@"操作1执行结束 == %@",[NSThread currentThread]);
}];
// 设置操作优先级 : 设置为最高
firstOp.qualityOfService = NSQualityOfServiceUserInteractive;
[self.queue addOperation: firstOp];
// 操作2
NSBlockOperation *secondOp = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 10; i++) {
NSLog(@"secondOp == %d %@",i,[NSThread currentThread]);
}
}];
// 设置操作优先级 : 设置为最低
secondOp.qualityOfService = NSQualityOfServiceBackground;
[self.queue addOperation: secondOp];
}
- 试例六
添加操作执行块
- (void)OperationDemo {
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
// 添加操作的执行块 : 这个执行块也是属于op对象的
// 当添加操作执行块后, 这个执行块里面的任务就新开子线程执行
[op addExecutionBlock:^{
NSLog(@"ExecutionBlock %@",[NSThread currentThread]);
}];
// 在没有添加执行块之前,这个操作是在当前线程执行
[op start];
}
打印结果:
2018-10-22 10:18:26.121270+0800 多线程Demo[16173:4731928] ExecutionBlock <NSThread: 0x6000002745c0>{number = 3, name = (null)}
2018-10-22 10:18:26.121270+0800 多线程Demo[16173:4730187] <NSThread: 0x600000261dc0>{number = 1, name = main}
得出结论:由打印的结果可以看出执行块里面的任务开启了新的线程执行,在没有添加执行块之前,这个操作是在当前线程执行的。
d. NSOperation的高级应用
- 队列的最大并发数
self.queue.maxConcurrentOperationCount = 3;
设置最大并发数为三,每次只能调度三个操作。
- 队列的暂停、继续和取消全部
// 队列暂停
self.queue.suspended = YES;
只能够暂停还没有执行的操作,正在执行的操作没有办法暂停。如果先暂停队列,再添加操作到队列,队列不会调度操作执行。
// 队列继续
self.queue.suspended = NO;
// 队列全部取消
[self.queue cancelAllOperations];
这个方法只能够取消还没有执行的操作,正在执行的操作没有办法取消。如果要取消,需要自定义NSOperation。队列取消全部操作时,会有一定的时间延迟。
- 操作依赖
- (void)dependencyDemo {
// 登陆->付费->下载
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"登陆");
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"付费");
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载");
}];
// 设置操作依赖
[op3 addDependency:op2];
[op2 addDependency:op1];
// [op1 addDependency:op3]; 注意1:不要搞成循环依赖
// 不同的队列之中的操作也可以设置操作依赖
[[NSOperationQueue mainQueue]addOperation:op1];
// 把操作添加到队列
[self.queue addOperations:@[op2,op3] waitUntilFinished:NO];
}
很多时候我们希望自己所执行的事情能按照顺序执行,比如我们我们下载一首付费的歌曲,需要我们按顺序执行登录-付费-下载的操作,由于这些操作我们放在后台去执行,所以它们执行的顺序是不确定的,看CPU怎么调度,所以我们为了达到我们的要求。就需要添加操作依赖来实现。
注意:
不能循环建立操作间依赖关系。否则,队列不调度操作执行。
操作间可以跨队列建立依赖关系。
要将操作间的依赖建立好了之后,再添加到队列中。