概述
优点:
- 把程序中耗时的任务放到后台去处理,如图片、视频的下载等;
- 充分发挥多核处理器的优势,并发执行让系统运行的更快、更流畅、用户体验更佳。
不足:
- 大量的线程操作会降低代码的可读性;
- 大量的线程需要更多的内存空间;
- 当多个线程对同一资源出现争夺的时候会出现线程安全问题。
目前实现多线程的技术有四种:pthread、NSThread、GCD和NSOperation。
- pthread:是一套基于C语言的通用多线程API,线程的生命周期需要我们手动管理,使用难度较大,所以我们几乎不会使用。
- NSThread:是一套面向对象的API,线程的生命周期需要我们手动管理,简单易用,可直接操作线程对象。
- GCD:是一套底层基于C语言的多线程API,自动管理生命周期,充分利用了多核处理器的优点。
- NSOperation:是一套底层基于GCD面向对象的多线程API,自动管理生命周期,并且比GCD多了一些更简单实用的功能。
在这里我们暂且不讨论pthread的使用,主要看看后面三个方案都是怎么应用的。
NSThread
一个NSThread对象就代表一个线程,使用NSThread有多种创建线程的方式:
1. 先创建再启动
// 创建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:@"Axe"];
// 启动
[thread start];
2. 直接创建并启动线程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"Axe"];
3. 创建并启动
[self performSelectorInBackground:@selector(run:) withObject:@"Axe"];
[NSThread sleepForTimeInterval:2.0];
从三种创建线程的方法可以看到:方法2和3都可以更方便的创建一个线程,并且自启动。而方法一可以很方便的拿到线程对象。
4.应用示例
以下载一张图片为例:
NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/203fb80e7bec54e753da379aba389b504fc26a7b.jpg"];
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImageWithURL:) object:url];
[thread start];
- (void)downloadImageWithURL:(NSURL *)url {
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&error];
if (error) {
NSLog(@"error = %@",error);
} else {
UIImage *image = [UIImage imageWithData:data];
[self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
}
}
GCD
GCD全称是Grand Central Dispatch,可译为“中枢调度器”。GCD是苹果公司为多核的并行运算提出的解决方案,它会自动利用更多的CPU内核来开启线程执行任务。
在了解GCD之前先明白同步/异步、并行/串行这几个名词的概念。
- 同步线程:在当前线程可立即执行任务,不具备开启线程的能力,会阻塞当前的线程,必须等待同步线程中的任务执行完并返回后,才会执行下一个任务。
- 异步线程:在当前线程结束后执行任务,具备开启新的线程的能力。
- 并行队列:允许多个任务同时执行。
- 串行队列:只有等上一个任务执行完毕后,下一个任务才会被执行。
创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
创建并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
创建同步+串行队列
//在当前线程,立即执行任务
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);
dispatch_sync(serialQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"%@",[NSThread currentThread]);
}
});
创建同步+并行队列
//在当前线程,立即执行任务
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(concurrentQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"%d --- %@",i, [NSThread currentThread]);
}
});
创建异步+串行队列
//另开启线程,多个任务顺序执行
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_async(serialQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"%d --- %@",i, [NSThread currentThread]);
}
});
dispatch_async(serialQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"%d --- %@",i, [NSThread currentThread]);
}
});
});
创建异步+并行队列
//另开启线程,多个任务一起执行,不分先后
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
dispatch_async(serialQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"%d --- %@",i, [NSThread currentThread]);
}
});
dispatch_async(serialQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"%d --- %@",i, [NSThread currentThread]);
}
});
});
应用示例
还是以下载一张图片为例
NSURL *url = [NSURL URLWithString:@"http://f.hiphotos.baidu.com/image/pic/item/203fb80e7bec54e753da379aba389b504fc26a7b.jpg"];
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.download.image", DISPATCH_QUEUE_CONCURRENT);
__weak typeof(self) weakSelf = self;
dispatch_async(concurrentQueue, ^{
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:url options:NSDataReadingMappedIfSafe error:&error];
if (error) {
NSLog(@"error = %@",error);
} else {
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.imageView.image = image;
});
}
});
GCD其他函数的应用
dispatch_barrier 栅栏
功能描述:先执行栅栏前面的队列,再执行栅栏中的队列,等待栅栏中的队列执行完毕后,才会执行栅栏后面的队列。
注意事项:栅栏在并行队列中使用才有它的意义,强行在无序执行的队列中创造出顺序执行的队列任务,当不能使全局的并行队列,一般会自己创建我们的并行队列
代码示例:
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"1-->%d --- %@",i, [NSThread currentThread]);
}
});
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"2-->%d --- %@",i, [NSThread currentThread]);
}
});
dispatch_barrier_async(concurrentQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"3-->%d --- %@",i, [NSThread currentThread]);
}
});
dispatch_async(concurrentQueue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"4-->%d --- %@",i, [NSThread currentThread]);
}
});
通过log输出可以看到,1和2队列的任务会先无序执行,在其两个队列中的任务执行完毕后,才会执行栅栏队列中的任务,最后才执行队列4。dispatch_after 延迟
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
});
dispatch_once 执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 执行一次
});
应用:常用用于单例中。
dispatch_apply 快速迭代
NSArray *array = @[@"a",@"b",@"c",@"d",@"e"];
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(array.count, globalQueue, ^(size_t idx) {
NSLog(@"array[%zu] = %@",idx, array[idx]);
});
dispatch_group 队列组
功能描述:将多个队列添加到一个队列组中,当队列组中的任务都执行完毕后,会通知我们执行结果。
代码示例:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_async(group, globalQueue, ^{
NSLog(@"任务1");
});
dispatch_group_async(group, globalQueue, ^{
NSLog(@"任务3");
});
dispatch_group_async(group, globalQueue, ^{
NSLog(@"任务2");
});
dispatch_group_notify(group, globalQueue, ^{
NSLog(@"所有任务都已完成");
});
dispatch_group_async(group, globalQueue, ^{
NSLog(@"任务4");
});
GCD定时器
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, globalQueue);
// 3s后定时器启动
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
// 每1秒执行一次回调
dispatch_source_set_timer(timer, start, 2.0 * NSEC_PER_SEC, 0);
// 计时
__block int count = 0;
dispatch_source_set_event_handler(timer, ^{
NSLog(@"%d", count);
if (count > 10) {
dispatch_cancel(timer);
}
count++;
});
dispatch_resume(timer);
NSOperation
NSOperation是一个抽象类,并不具备封装操作的能力,我们必须使用它的子类,为此系统也提供了NSInvocationOperation和NSBlockOperation两个子类供我们使用。当然我们也可以继承NSOperation,创建我们的子类,实现内部相应的方法。
NSInvocationOperation
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImageWithURL:) object:url];
[invocationOperation start];
默认情况下,调用start方法后,并不会开启一个新的线程去执行selector,而是在当前线程同步的执行操作,只有将NSOperation添加到NSOperationQueue中,才会执行异步操作。
NSBlockOperation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
// 在主线程中执行
NSLog(@"操作1 --- %@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
// 在子线程中执行
NSLog(@"操作2 --- %@", [NSThread currentThread]);
}];
// 添加额外的任务数大于1才会异步执行
[blockOperation addExecutionBlock:^{
// 在子线程中执行
NSLog(@"操作3 --- %@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
// 在子线程中执行
NSLog(@"操作4 --- %@", [NSThread currentThread]);
}];
[blockOperation start];
Operation的其它用法
执行操作
执行一个Operation有两种方式,一种是手动调用start,这种方法调用会在当前线程进行同步执行,因此在主线程里面一定要小心调用,不然就会堵塞主线程。另一种是自动执行,只要将operation添加到operationQueue中,就会尽快执行操作。
取消操作
NSOperation开始执行操作后,会默认一直到操作完成,当然中途我们也可以调用cancel取消操作。
在调用cancel方法的时候,只是将cancelled设置为了YES,因此在每个操作开始前,或者在每个有意义的实际操作完成后,都要先检测isCancelled是否被设置成了YES, 如果已经取消了,那么后面的操作就不用在执行了。
操作完成后的操作
如果想在NSOperation执行完操作后做一些事情,可以调用completionBlock设置,在操作完成后就会回调block里面的内容。
自定义Operation
如果系统提供的NSInvocationOperation和NSBlockOperation两个子类不能满足需求时,我们通过继承自定义之类,并添加需要执行的操作。
这里我们仍然以下载图片为例:
//
// MyOperation.h
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef void(^MyDownloadFinishedBlock)(UIImage *image);
@interface MyOperation : NSOperation
@property (copy, nonatomic, readonly) NSString *imageURL;
- (instancetype)initWithURLString:(NSString *)urlString downloadFinishedBlock:(MyDownloadFinishedBlock)downloadFinishedBlock;
@end
//
// MyOperation.m
//
#import "MyOperation.h"
@interface MyOperation ()
@property (copy, nonatomic) MyDownloadFinishedBlock downloadFinishedBlock;
@end
@implementation MyOperation
- (instancetype)initWithURLString:(NSString *)urlString downloadFinishedBlock:(MyDownloadFinishedBlock)downloadFinishedBlock {
self = [super init];
if (self) {
_imageURL = urlString;
_downloadFinishedBlock = downloadFinishedBlock;
}
return self;
}
- (void)main {
// 如果时在异步线程中执行操作,即main方法在异步线程中调用,那么将无法访问主线程的自动释放池,因此创建一个属于当前线程的自动释放池
@autoreleasepool {
// 在main方法中定期的调用isCancelled方法检测操作是否已经被取消
// 在执行任何实际的工作之前,也就是main方法的开头,就要检测操作是否已经被取消
// 在执行一段耗时的操作后也需要检测操作是否已经被取消
if (self.isCancelled) {
return;
}
NSURL *url = [NSURL URLWithString:_imageURL];
// 此处就是通过网络获取图片data,是比较耗时的操作,所以在后面就需要检测操作是否已经被取消
NSData *imageData = [NSData dataWithContentsOfURL:url];
if (self.isCancelled) {
url = nil;
_imageURL = nil;
return;
}
UIImage *image = [UIImage imageWithData:imageData];
if (self.isCancelled) {
image = nil;
return;
}
if (_downloadFinishedBlock) {
_downloadFinishedBlock(image);
}
}
}
@end
MyOperation *operation = [[MyOperation alloc] initWithURLString:@"http://f.hiphotos.baidu.com/image/pic/item/203fb80e7bec54e753da379aba389b504fc26a7b.jpg" downloadFinishedBlock:^(UIImage *image) {
NSLog(@"%@",[NSThread currentThread]);
[_imageView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:YES];
}];
[operation start];
说明:如果你要执行一个同步操作,那只需要重写main方法,在里面添加必要的操作。如果执行一个异步的操作,那就需要重写start方法,因为在你把操作添加进去后系统会自动调用start方法,这时将不在调用mian里面的操作。
NSOperationQueue
一个NSOperation对象可以通过start方法来执行任务,默认是同步执行的。也可以将其添加到NSOperationQueue操作队列中去执行,它是异步执行的。
添加NSOperation到NSOperationQueue中
不管通过那种方式添加,只要将operation添加到operationQueue中,通常短时间内就会得到执行(异步)。但是如果存在依赖,或者整个operationQueue被暂停等原因,也可能需要等待。
添加一个operation
[operationQueue addOperation:invocationOperation];
添加一组operation
[operationQueue addOperations:@[invocationOperation, blockOperation] waitUntilFinished:YES];
添加一个block形式的operation
[operationQueue addOperationWithBlock:^{
NSLog(@"任务");
}];
添加依赖
概述
所谓依赖就是说,当某个operation对象需要依赖与其它operation对象才能完成时,就可通过addDependency方法添加一个或者多个依赖对象,只有所有依赖的对象都已经完成操作后,当前的operation对象才开始执行操作,当然也可以通过removeDependency方法来移除这种依赖关系。
依赖方式
既可以在同一个operationQueue中不同operation对象添加依赖,也可以在不同的operationQueue之间不同operation对象之间添加依赖,operation对象会管理自己的依赖关系。
限制依赖
虽然可以在一个queue中添加依赖,也可以在不同的queue中添加依赖,但是要特别注意的是,不能添加环形依赖,即a依赖b,b也依赖a。
应用示例:
这里以修改用户头像为例,模拟从用户上传头像到显示头像的过程。
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1.上传头像");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2.从服务器请求上传头像的url");
}];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3.通过url下载头像");
}];
[operationQueue addOperation:blockOperation1];
[operationQueue addOperation:blockOperation2];
[operationQueue addOperation:blockOperation3];
这样的顺序显然不符合正常的逻辑顺序,这时候,我们就可以通过添加依赖,达到我们说期望的顺序逻辑。
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1.上传头像");
}];
NSBlockOperation *blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2.从服务器请求上传头像的url");
}];
NSBlockOperation *blockOperation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3.通过url下载头像");
}];
// 3 -> 2 2 -> 1 (3 - 2 - 1)
[blockOperation3 addDependency:blockOperation2];
[blockOperation2 addDependency:blockOperation1];
[operationQueue addOperation:blockOperation1];
[operationQueue addOperation:blockOperation2];
[operationQueue addOperation:blockOperation3];
打印顺序如下:1 - 2 - 3
这样就符合我们期望的逻辑,即用户先上传图片并完成后,再从服务器获取该用户头像的url地址,最后通过url地址下载相应的头像并显示。