最近整理的iOS多线程方面的知识点,iOS中总共有4种实现多线程的方案,但是pthread是基于C语言并且不太好用,所以很少人用,所以也没啥好讲的。
以下内容包含NSThread、GCD和NSOperation
欢迎指错以及补充
NSThread
3种创建线程方法
- NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
- [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
- [self performSelectorInBackground:@selector(run) withObject:nil];
3种方法对比
- 第一种方法可以拿到线程对象thread,可以设置thread的一些特性,比如优先级,名称;后两种方法均拿不到线程对象。
GCD
1. 6种组合:
- 同步函数+串行队列:不会开启子线程,所有的任务在主线程串行执行
- 同步函数+并发队列:不会开启子线程,所有的任务在主线程串行执行
- 同步函数+主队列:会发生死锁
- 异步函数+串行队列:会开启一条子线程,所有的任务在子线程串行执行
- 异步函数+并发队列:会开启n条子线程,所有的任务在n条子线程并发执行
- 异步函数+主队列:不会开启子线程,所有的任务在主线程中串行执行
2. 一次性函数:
static dispatch_once_t onceToken;
// NSLog(@"%ld", onceToken);
// 原理是判断onceToken的值,若等于0则执行,若等于-1则不执行
dispatch_once(&onceToken, ^{
NSLog(@"once");
});
3. 延迟执行:
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
[NSTimer scheduledTimerInterval:2.0 target:self selector:(run) userInfo:nil repeats:YES];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
NSLog(@"%@", [NSThread currentThread]);
});
4. 队列组
- 创建一个队列组
dispatch_group_t group = dispatch_group_create();
- 创建一个队列
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
- 封装任务
dispatch_group_async(group, queue, ^{
NSURL *url1 = [NSURL URLWithString:@"https://ww3.sinaimg.cn/mw1024/bfa7a89ejw1er5utf4cr9j20qo0zke4v.jpg"];
NSData *imageData1 = [NSData dataWithContentsOfURL:url1];
self.image1 = [UIImage imageWithData:imageData1];
NSLog(@"1-----%@", [NSThread currentThread]);
});
---------------------------------------------------------------
dispatch_group_async(group, queue, ^{
NSURL *url2 = [NSURL URLWithString:@"https://ww4.sinaimg.cn/mw1024/bfa7a89ejw1erp7r1nttgj20qo0zkq8g.jpg"];
NSData *imageData2 = [NSData dataWithContentsOfURL:url2];
self.image2 = [UIImage imageWithData:imageData2];
NSLog(@"2-----%@", [NSThread currentThread]);
});
- 监听通知(当队列组中的任务执行完毕后,创建图形上下文)
dispatch_group_notify(group, queue, ^{
UIGraphicsBeginImageContext(CGSizeMake(300, 300));
[self.image1 drawInRect:CGRectMake(0, 0, 150, 300)];
[self.image2 drawInRect:CGRectMake(150, 0, 150, 300)];
NSLog(@"combine-----%@", [NSThread currentThread]);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"UI-----%@", [NSThread currentThread]);
});
});
5. 快速迭代
- (void)test2
{
NSString *from = @"/Users/AngusGray/Desktop/from";
NSString *to = @"/Users/AngusGray/Desktop/to";
NSArray *subPaths = [[NSFileManager defaultManager] subpathsAtPath:from];
dispatch_apply(subPaths.count, dispatch_get_global_queue(0, 0), ^(size_t i) {
NSString *fromFullPath = [from stringByAppendingPathComponent:subPaths[i]];
NSString *toFullPath = [to stringByAppendingPathComponent:subPaths[i]];
[[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
NSLog(@"%@-----%@-----%@", fromFullPath, toFullPath, [NSThread currentThread]);
});
}
6. 线程间通信
- (void)viewDidLoad {
[super viewDidLoad];
// 异步函数 + 串行|并发队列
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 设置URL
NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1552289582393&di=bcd6e443745c4839107b46e260e9ea32&imgtype=0&src=http%3A%2F%2Fimg.12584.cn%2Fimages%2F3718642-20170112230345230.jpg"];
// 将URL中的图片转换为二进制数据
NSData *imageData = [NSData dataWithContentsOfURL:url];
NSLog(@"download-----%@", [NSThread currentThread]);
// 将二进制数据转化为图片(在主线程中进行)
// 异步函数|同步函数 + 主队列
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = [UIImage imageWithData:imageData];
NSLog(@"UI-----%@", [NSThread currentThread]);
});
});
}
7. 栅栏函数
dispatch_barrier_async(queue, ^{
NSLog(@"+++++");
});
NSOperation
1. 自定义队列和主队列
- 自定义队列:[[NSOperationQueue alloc] init];
特点:默认并发队列,但是可以控制让它成为一个串行队列。 - 主队列:[NSOperationQueue mainQueue];
特点:串行队列,和主线程相关(凡是放在主队列中的任务都在主线程执行)。
2. 创建步骤
- 创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
- 封装操作对象
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1-----%@", [NSThread currentThread]);
}];
- 把操作添加到队列中
// 该方法内部会自动调用start方法
[queue addOperation:op1];
- 简便方法:该方法内部首先会把block中的任务封装成一个操作(Operation),然后把该操作直接添加到队列
[queue addOperationWithBlock:^{
NSLog(@"op1-----%@", [NSThread currentThread]);
}];
3. 特点
- 可以设置最大并发数
queue.maxConcurrentOperationCount = 1; // 等同于串行执行
- 可以暂停队列中的操作(只能暂停正在执行操作后面的操作,当前操作不可分割必须执行完毕
[self.queue setSuspended:YES];
- 恢复暂停的操作
[self.queue setSuspended:NO];
- 取消所有操作(只能取消队列中所有正在等待的操作
[self.queue cancelAllOperations];
- 将操作添加到队列可使用数组的方式(如果使用YES,则当前线程阻塞直到所有操作完成)
[queue addOperations:@[op1, op2, op3] waitUntilFinished:NO];
- 可以通过重写内部的main方法类可以自定义操作任务(自定义操作的好处:代码复用)
-(void)main
{
for (int i = 0; i < 10000; i++) {
NSLog(@"main1--%d--%@", i, [NSThread currentThread]);
}
//官方建议:在自定义操作的时候每执行完一次耗时操作就判断一下当前操作是否被取消,如果被取消则直接返回
if (self.cancelled == YES) {
return;
}
for (int i = 0; i < 10000; i++) {
NSLog(@"main2--%d--%@", i, [NSThread currentThread]);
}
if (self.cancelled == YES) {
return;
}
for (int i = 0; i < 10000; i++) {
NSLog(@"main3--%d--%@", i, [NSThread currentThread]);
}
}
4. NSBlockOperation
- 本身不开子线程,添加额外任务时会开子线程
- 当操作中任务数量>1时,会开启多条子线程和当前线程一起工作
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1-----%@", [NSThread currentThread]);
}];
[op1 addExecutionBlock:^{
NSLog(@"1-----%@", [NSThread currentThread]);
}];
[op1 start]; // 需要手动开始
5. 线程间通信
- 在子线程中调用主线程执行任务
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
do something;
}];
6. 操作依赖和监听
- 给操作添加依赖op4->op3->op2->op1
[op1 addDependency:op2];
[op2 addDependency:op3];
[op3 addDependency:op4];
- 当操作完成可使用completionBlock监听
op4.completionBlock = ^{
do something;
};
GCD对比NSOperation
- GCD是纯C语言的API,而NSOperation是OC的对象。
- GCD中任务用块(block)封装,而NSOperation中任务用操作封装,block比操作更加轻量级。
- GCD有栅栏函数、延迟执行和快速迭代
- NSOperationQueue可以方便地调用cancel方法来取消某个操作,而GCD中的任务是无法被取消的。
- NSOperation可以方便地指定操作间的依赖关系
- NSOperation可以通过KVO达到对操作的精细控制,比如监听操作是否被取消或者已经完成。
- NSOperation可以方便地指定操作优先级。
- 通过自定义NSOperation的子类可以实现操作复用。