重点:
NSThread
多线程基础、pthread、开启线程的3种方式
线程的状态、线程安全问题、线程间的通信
GCD
同步方法和异步方法、队列的使用、线程间的通信
延迟执行、一次性代码、队列组、单例模式-ARC、单例模式-MRC、用宏抽取单例模式
NSOperation
NSOperation和NSOperationQueue的概念理解、NSInvocationOperation、NSBlockOperation
NSOperationQueue的常见方法、最大并发数、操作依赖、队列的取消\暂停\恢复
一、 GCD
libdispatch.tbd
#import <dispatch/dispatch.h> 主头文件
1、简介
GCD是Grand Central Dispatch的简称,它是基于C语言的。如果使用GCD,完全由系统管理线程,我们不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度你的任务,系统直接提供线程管理。
1)GCD的2个重要概念是任务、队列
任务是”执行什么操作“
执行任务:
异步任务:dispatch_async(queue, ^{ NSLog(@"开启了一个异步任务,当前线程:%@", [NSThread currentThread]); }); //可以在新的线程中执行任务,具备开启新线程的能力。--如果是主队列的话,将不开启新的线程
同步任务:dispatch_sync(queue, ^{ NSLog(@"开启了一个同步任务,当前线程:%@", [NSThread currentThread]); }); //只能在当前线程中执行任务,不具备开启新线程的能力
队列:用来存放任务
1)队列类型:
并发队列(concurrent dispatch queue则尽可能多地启动任务并发执行)。--并发功能只有在异步函数(dispatch_async)才有效。
串行队列(serial dispatch queue一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务)
小结
同步、异步的主要影响:能不能开启新的线程
并发、串行的主要影响: 任务的执行方式
2)GCD 使用的2个步骤:(最有价值的用法:将异步任务添加到并发队列中)
定制任务:确定想做的事情
将任务添加到队列中:GCD会自动将队列中的任务取出,放到对应的线程中执行。
2、创建和管理dispatch queue
1).获取并发队列
系统默认给每个应用提供了三个并发dispatch queue,整个应用内全局共享,三个queue的区别是优先级。
你不需要显式地创建这些queue,只需使用dispatch_get_global_queue函数来获取这三个queue:
// 获取默认优先级的全局并发dispatch queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue;第二个参数保留参数,默认0即可
*/
2)创建串行Dispatch Queue (serial dispatch queue) 两种方式:
方式一:
// dispatch_queue_t queue = dispatch_queue_create("com.hisunpay.queue", NULL);
//如果是非ARC(automatic reference counting mode),需要释放创建的队列'release' is unavailable: not available in automatic reference counting mode
// dispatch_release(queue);
方式二:使用主队列,即跟主线程相关联的队列,是GCD自带的一种特殊的串行队列。放到主队列的任务,都会放到主线程中执行--通常利用“主队列”来和主线程进行通信
#define HLWS(weakself) __typeof(&*self)(weakself)= self
#define HLGlobaQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define HLMainQueue dispatch_get_main_queue()
//2)获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
/** 线程间的通信*/
- (void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//子线程下载图片
__weak ViewController *vc = self;
dispatch_async(HLGlobalQueue, ^{
//下载图片
NSString *url = @"http://avatar.csdn.net/6/4/8/1_q199109106q.jpg";
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]];
//回到主线程
dispatch_async(HLMainQueue, ^{
//任务
// [vc.button setBackgroundImage:image forState:UIControlStateNormal];
[vc.button setImage:image forState:UIControlStateNormal];// vc.button.buttonType=UIButtonTypeSystem 是不能设置image的
});
});
}
小结
p s: 1)主队列中不能添加同步任务,会导致死锁。
因为:同步任务A添加到当前正在执行的串行queue,会造成,新的同步任务A执行完才能结束正在执行的任务B,但当前执行的任务B执行结束之后才会执行新的同步任务A,就产生了死锁。
3、添加任务到queue
要执行一个任务,你需要将它添加到一个适当的dispatch queue,你可以单个或按组来添加,也可以同步或异步地执行一个任务。一旦进入到queue,queue会负责尽快地执行你的任务。
一般可以用一个block来封装任务内容。
1)dispatch_sync;dispatch_async。
添加任务到队列 Expand source
dispatch_sync(queue, ^{
//任务
NSLog(@"%s--开启了一个同步任务--%@",__func__,[NSThread currentThread]);//_block_invoke--开启了一个同步任务--<NSThread: 0x7f8f5b5014b0>{number = 1, name = main}
});
/* dispatch_async 可以在新的线程中执行任务,具备开启新线程的能力*/
dispatch_async(queue, ^{
//任务
NSLog(@"%s--开启了一个异步任务%@",__func__,[NSThread currentThread]);//_block_invoke_2--开启了一个异步任务<NSThread: 0x7fa4a8473480>{number = 2, name = (null)}
});
4、延迟执行
延迟执行的两种方法 Expand source
#pragma mark - 延迟执行总结
/** 使用GCD函数:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
<#code to be executed after a specified delay#>
}); */
- (void)afterDelay3{
/**主队列 */
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%s- -- thread:%@",__func__,[NSThread currentThread]);
});
/*
dispatch_queue_priority_t. 并发队列,自动开启新线程
*/
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"%s- -- thread:%@",__func__,[NSThread currentThread]);//_block_invoke- -- thread:<NSThread: 0x7a0180f0>{number = 2, name = (null)}
});
}
/** 调用NSObject 的 - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
*/
- (void)afterDelay1{
//nvokes a method of the receiver on the current thread using the default mode after a delay. 不会卡住当前线程
[self performSelector:@selector(downloadWithName:) withObject:@"http://" afterDelay:3];
}
- (void)afterDelay0{
//不要使用sleep,缺点:卡住线程
[NSThread sleepForTimeInterval:3];
}
5、一次性代码
dispatch_once函数 Expand source
/** dispatch_once 函数能保证:某段代码在程序运行过程中只被执行一次 */
- (void) downLoad{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//一次性代码
NSLog(@"%s----下载图片",__func__);
});
}
6、暂停和继续queue()
我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;
使用dispatch_resume函数继续dispatch queue。
1)调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。
2)挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。
6、Dispatch Group的使用
dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。
而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行
例子:合并下载的两张图片
0、创建一个组组dispatch_group_create
1、关联下载任务到group dispatch_group_async--下载两张图片放在同不同的任务中完成
2、等待组中的任务执行完毕,回到主线程执行block回调 --dispatch_group_notify函数用来指定一个额外的block,该block将在group中所有任务完成后执行
组的使用 Expand source
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
HLWS(vc);
/*
合并图片,
0、创建一个组组dispatch_group_create
1、关联一个任务到group dispatch_group_async--下载两张图片放在同不同的任务中完成
2、等待组中的任务执行完毕,回到主线程执行block回调 --dispatch_group_notify函数用来指定一个额外的block,该block将在group中所有任务完成后执行
*/
dispatch_async(HLGlobaQueue, ^{
NSLog(@"%s--0-----%@",__func__,[NSThread currentThread]);//block_invoke--0-----<NSThread: 0x7b648350>{number = 2, name = (null)}
//创建一个队列组
dispatch_group_t group = dispatch_group_create();
/*
blocks 访问权限
1)blocks可以访问局部变量a,但是不能修改。
原因是在编译期间确定的,编译器编译的时候把a的值复制到block作为一个新变量(假设是a‘ = 10),此时a'和a是没有关系的。
2)如果要修改就要加关键字:__block或者static*/
__block UIImage *image1;
static UIImage *image2;
//下载图片任务关联到group
dispatch_group_async(group, HLGlobaQueue, ^{
//下载第一张图片
NSLog(@"%s-- 1-%@",__func__,[NSThread currentThread]);//]_block_invoke3-- 1-<NSThread: 0x7b648350>{number = 2, name = (null)}
NSString *str1 = @"http://avatar.csdn.net/6/4/8/1_q199109106q.jpg";
image1=[vc imageWithUrl:str1];
});
dispatch_group_async(group, HLGlobaQueue, ^{
NSLog(@"%s--2-----%@",__func__,[NSThread currentThread]);//_block_invoke11--2-----<NSThread: 0x7c9553b0>{number = 3, name = (null)}
NSString *str2 = @"http://avatar.csdn.net/D/9/E/2_u011018979.jpg";
image2= [vc imageWithUrl:str2];
});
//3.图片下载完之后开始绘制图片-- // 等待组中的任务执行完毕,回到主线程执行block回调
dispatch_group_notify(group, HLGlobaQueue, ^{
NSLog(@"%s-- 3-%@",__func__,[NSThread currentThread]);//_block_invoke19-- 3-<NSThread: 0x7b2525c0>{number = 2, name = (null)}
//绘制图片
//开启上上文
UIGraphicsBeginImageContextWithOptions(image1.size, NO, 0.0);
[image1 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
[image2 drawInRect:CGRectMake(image1.size.width-image2.size.width*0.5, image1.size.height-image2.size.height*0.5, image2.size.width*0.5, image2.size.height*0.5)];
//获取图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();
//回到主队列
dispatch_async(HLMainQueue, ^{
NSLog(@"%s-- 4-%@",__func__,[NSThread currentThread]);//_block_invoke_2-- 4-<NSThread: 0x7a650590>{number = 1, name = main}
//显示图片
[vc.imageView setImage:image];
});
});
});
}