iOS多线程
线程基础
现代的操作系统,包括iOS,都支持线程(Thread)的概念。每个进程可以包含多个线程,借助多个CPU的内核,可以同时处理多个任务。即便是在早期单核CPU的时代,操作系统也可以把CPU资源在多个线程之间进行切换,给用户的感觉是在“同时”处理多个任务。
进程与线程
每个应用程序都有一个进程。例如在Mac系统中,可以通过活动监视器来查看当前Mac正在运行的进程。每个进程可以包含多个线程,这些线程可以同时运行,并各自处理一些任务。早期的CPU只有一个核心,因此会交替处理各个线程中的任务,让用户产生一种多任务同时处理的“感觉”。当CPU拥有多个内核时,各个线程就可以分配到不同的CPU内核中进行处理,即为真正意义上的多任务同时处理,如图所示:
对于任意一个iOS应用,程序运行起来后,默认会产生一个主线程(MainThread)
。主线程专门用来处理UIKit对象的操作,如界面的显示与更新、处理用户交互事件等(请牢记这点,所有与UI相关的操作都要在主线程中进行)。
对于一个应用来说,之所以引入多个线程,很大程度上是由于存在一些操作是非常耗时的,例如:发送网络请求并等待服务器的响应,这种耗时操作是不能够放在主线程中进行操作的,因为在等待的时间内主线程被使用,用户是不能做任何交互动作的,因而会极大影响用户体验。对于耗时的操作,需要再另外创建一个线程,放到后台处理,当处理完成得到结果后,再返回主线程去设置UI界面,这就涉及线程间通信的相关知识。
多线程同时处理任务,还会涉及线程安全(thread-safe)
的问题,当多个线程对一个对象进行同时操作时,就会影响结果的正确性。因此,线程对某个对象操作时,需要使用到“锁”的机制,即当一个线程操作一个对象的过程中,会给这个对象上锁,其他线程就不能够访问该对象了。加锁虽然解决了线程安全的问题,但带来的另外一个弊端就是影响了程序运行的效率。
当给一个自定义类中添加属性时,属性关键字其中就有atomic
和nonatomic
的区分,其中,atomic
是线程安全的,当有线程访问这个属性时,会为该属性的setter
方法加锁,atomic
是默认值。但是在实际的开发中,都会把给属性设置nonatomic
关键字,因为对于移动设备来说,效率更加重要,但也需要程序员注意线程安全问题。
NSThread类
NSThred类是苹果官方提供的管理线程的类,提供了一些线程管理的方法。但是随着GCD和NSOperation的推出,NSThread的使用场景已经大大减少。在实际的开发中,偶尔会使用NSThread类来获取一些线程信息。常用的一些方法如下:
+ (NSThread *) currentThread;
- 获取主线程对象。
+ (NSThread *) mainThread;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti
GCD
GCD(Grand Central Dispatch)技术,苹果首先应用于OS X中,随后在iOS中也引入了GCD技术。特别是苹果公司把CPU升级为多核后,GCD的使用变得更加广泛和重要。相比于NSThread,GCD中已经完全屏蔽了有关线程的概念,而是引入了任务和队列
,把任务放到队列中执行,指定任务和队列的类型。其他有关线程管理的事务完全交由GCD来处理,大大简化了多任务开发的难度。
认识GCD
GCD是苹果公司推出的专门用于简化多线程编程的技术。在GCD中,程序员已经不再需要去关心有关线程的操作(如:线程创建、线程销毁、线程调度),而是引入了任务和队列两个核心概念。
由于GCD是苹果公司推出的技术,因此GCD能够很好地调度苹果设备的CPU资源,不论是在Mac平台,还是在iOS平台。特别是在iPhone中引入多核CPU之后,GCD的使用就变得越发重要。
由于GCD对线程管理进行了封装,因此,当工程师使用GCD时,只需要把任务(通常封装在一个Block中)添加到一个队列中执行,有关线程调度的工作完全交由GCD完成。
GCD对队列中的任务,按照“先进先出”的原则,根据任务添加到队列的顺序来对队列进行处理,GCD会根据任务和队列的类型,自动在多个线程之间分配工作。
任务
在GCD中,需要处理的事务统一使用Block封装起来,称为任务。任务有两种类型,同步任务和异步任务。通过调用不同的函数,来设置任务的类型。同时,任务编写在函数的Block参数中。
异步任务:执行任务时,会在另外的线程中执行,即可能会创建新的线程。
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
同步任务:执行任务时,会在当前的线程中执行,当前线程有可能是主线程,也有可能是子线程。
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
队列
GCD中,队列是一个重要概念。系统提供了若干预定义的队列,其中包括可以获取应用程序的主队列(任务始终在主线程上执行,与更新UI相关的操作必须在主队列中完成)。另外,工程师可以自由创建不同类型的队列,例如:并行队列和串行队列,队列的类型决定了任务的执行方式。GCD队列严格按照“先进先出”的原则,添加到GCD队列中的任务,始终会按照加入队列的顺序被执行。
并行队列
:并行队列中的任务可以在多个线程之间分配执行,分配的原则由GCD控制,因此,并行队列中的任务,虽然执行时按照先进先出进行分配的,但由于各个任务被分配到不同的线程执行,因此其完成时间有可能不同,即:后分配的任务有可能先执行完成;并发队列一定需要和异步执行的任务(使用dispatch_async()
)结合起来使用才有意义。
串行队列
:串行队列中的任务是按照顺序一个一个完成的,当一个任务完成后,才去执行下一个任务;因此,串行队列对应一个线程执行。
主队列
:主队列也是一个串行队列,主队列中的任务都在主线程中执行。
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);
dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t _Nullable attr);
- 获取主队列。
dispatch_get_main_queue(void);
队列与任务结合
在GCD中存在队列和任务两个核心概念,同时队列又分为并行队列、串行队列和主队列,任务包括异步任务和同步任务。GCD的使用方法就是把任务放到队列中执行,因而根据不同的任务类型和队列类型,就会存在6种组合。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//获取一个并行队列,其队列优先级为默认,保留参数置为0
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建异步任务,并放到并行队列中执行
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task2:%d", i);
}
NSLog(@"task2----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task3:%d", i);
}
NSLog(@"task3----%@", [NSThread currentThread]);
});
}
运行结果如图所示。异步任务+并行队列组合情况下,每个任务会在不同的线程中同时执行。
- 异步任务+串行队列
对于异步任务放在串行队列中执行时,任务只会在一个新开的线程中,按照顺序进行执行。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//获取一个串行队列,其字符串标识符为com.1101iOS,枚举类型设为NULL会创建一个serial队列
dispatch_queue_t queue = dispatch_queue_create("com.1101iOS", NULL);
//创建异步任务,并放到串行队列中执行
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task2:%d", i);
}
NSLog(@"task2----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task3:%d", i);
}
NSLog(@"task3----%@", [NSThread currentThread]);
});
}
运行结果如图所示。可以看到,所有任务都在一个线程中执行,并且完成一个后,再执行下一个。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//创建异步任务
dispatch_async(mainQueue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1----%@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task2:%d", i);
}
NSLog(@"task2----%@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task3:%d", i);
}
NSLog(@"task3----%@", [NSThread currentThread]);
});
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//获取并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//同步执行
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task2:%d", i);
}
NSLog(@"task2----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task3:%d", i);
}
NSLog(@"task3----%@", [NSThread currentThread]);
});
}
运行结果如图所示,由于viewDidLoad方法是在主线程中执行的,因此,创建的同步任务也在主线程中执行。
- 同步任务+串行队列
同步任务放在串行队列中执行,任务会在当前线程依次执行。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.1101iOS", NULL);
//同步执行
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task2:%d", i);
}
NSLog(@"task2----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task3:%d", i);
}
NSLog(@"task3----%@", [NSThread currentThread]);
});
}
运行结果如图所示,由于viewDidLoad方法是在主线程中执行的,因此,创建的同步任务也在主线程中执行。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//同步执行
dispatch_async(mainQueue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task1:%d", i);
}
NSLog(@"task1----%@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task2:%d", i);
}
NSLog(@"task2----%@", [NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
for (int i = 0; i < 2; i++) {
NSLog(@"task3:%d", i);
}
NSLog(@"task3----%@", [NSThread currentThread]);
});
}
当调用dispatch_sync会阻塞线程,直到任务(Block)执行完成之后才能继续执行。主线程中初始时需要执行的代码如图A所示。主线程需要执行1、2、3这几块代码。
图A
执行完1之后主线程需要执行的代码如图B所示。将任务(Block)添加到主线程中去执行。同时阻塞主线程,直到任务(Block)执行完成。此时Block 1等待3的代码执行完成,而代码1需要等Block 1执行完成。这就造成了1和Block 1相互等待,成了死锁。
图B
线程间通信
由于UI的更新和操作是由主线程负责的,因此,当使用子线程获取到数据时(例如通过网络获取得到了服务器返回的数据),需要返回主线程对UI界面进行更新,这时就涉及线程间的通信。
-
线程间通信简介
在涉及网络数据获取的过程中,一般会使用异步任务+并发队列进行数据获取。当获取到网络服务器返回的数据后,需要在主线程中把数据显示在屏幕上,因此这就涉及线程间的通信。在并行队列中的任务还可以嵌套子任务,嵌套的子任务可以设置为在主线程中执行的任务。 -
示例代码
下面的示例代码模拟了一个从网络获取一张图片的数据,并在屏幕上显示在一个UIImageView控件上的过程。在这个案例中,设置一个网络下载的异步任务,并放在并行队列中执行。此时,GCD会在一个新的线程中执行下载任务,当网络图片下载完成后,由于涉及更新UI,所以必须嵌套一个子任务在主队列中执行,如图所示。
由于涉及发送网络请求,需要提前在info.plist文件中设置App Transport SecuritySettings参数,如下所示。
在按钮的点击方法中,在异步任务+并行队列中实现网络图片的下载操作,并嵌套一个子任务在主线程中更新界面:
- (void) downloadImage {
//获取并行队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"0---%@", [NSThread currentThread]);
//异步任务
dispatch_async(queue, ^{
//从网络下载图片
NSString *urlString = @"https://img-blog.csdnimg.cn/75ccdf747f2a4d259bbb2b1c26110878.jpeg";
NSURL *url = [NSURL URLWithString:urlString];
NSData *imageData = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData];
NSLog(@"1---%@", [NSThread currentThread]);
//返回主线程设置UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"2---%@", [NSThread currentThread]);
});
});
NSLog(@"3---%@", [NSThread currentThread]);
}
通过日志打印的顺序可以看出程序执行的顺序,即:网络下载任务是在新的线程中执行的,并且图片下载完毕后,回到主线程设置图片显示。
队列组dispatch group
在使用GCD进行任务操作时,有时会希望若干个任务执行之间有先后执行的依赖关系,例如,当A、B两个异步任务完成后,再去完成C任务,这时就可以使用队列组dispatch group来完成。
- 队列组基本介绍
在串行队列中,任务是按照进入队列的顺序依次执行,因此任务和任务之间是有明确的先后顺序的。但是对于并行队列的任务来说,由于任务会被自动分配到不同的线程中执行,因此任务完成的顺序是不确定的。假如希望给并行队列中的任务设置执行顺序,例如,当任务A和任务B完成后,再去完成任务C,就需要使用到任务组dispatch group。
创建队列组:
dispatch_group_t group = dispatch_group_create();
向队列组中插入一个异步任务:
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
队列组中其他任务执行完成后,执行的任务,通常可以用来设置UI界面:
dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
队列组中的异步任务执行会在另外的进程中执行,等所有的任务都执行完成后,会通知执行dispatch_group_notify()中的任务。
延迟执行操作
在开发过程中,有时会希望把一些操作封装起来延迟一段时间后再执行。iOS开发中,有两种常用的方法可以实现延迟执行:一种是使用GCD;另外一种是使用NSRunLoop类中提供的方法。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"延迟2.0秒");
});
@interface NSObject (NSDelayedPerforming)
- (void) performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void) performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
@end
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(printLog) withObject:nil afterDelay:2.0];
}
- (void) printLog {
NSLog(@"延迟2秒后打印出来的日志");
}
NSOperation
GCD的核心是C语言写的系统服务,而NSOperation底层是通过GCD实现,也可以说NSOperation是GCD更高层次的抽象,这是他们之间最本质的区别。因此如果希望自定义任务,建议使用NSOperation。
简介
NSOperation在iOS 4后也基于GCD实现,但是相对于GCD来说可控性更强,并且可以加入操作依赖。NSOperation是一个抽象类,因此系统提供了NSBlockOperation和NSInvocationOperation两个子类,并且可以创建继承自NSOperation的自定义类。相比于GCD,NSOperation更加面向对象,开发者除了不需要去了解线程相关的概念之外,甚至连GCD中需要了解的异步/同步、并行/串行都不太需要深入了解,开发者只要懂得任务和队列即可。
基本使用
- NSOperation的类族
由于NSOperation是一个抽象类,因此不能够直接使用NSOperation,但苹果提供了两个NSOperation的子类,NSBlockOperation和NSInvocationOperation,除此之外,还可以使用自定义CustomOperation
类。如下:
对于NSBlockOperation类来讲,可以把任务封装在一个Block块之内。NSBlockOperation类提供了如下操作方法,用于创建一个NSBlockOperation对象,任务封装在Block中。
+ (instancetype)blockOperationWithBlock:(void (^)(void))block;
对于NSInvocationOperation类来说,需要执行的任务直接指定已定义的方法。
- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
- 无队列情况下执行任务
在不创建队列的情况下,可以直接调用NSOperation类的start方法来执行某个任务。该任务是在当前线程进行执行的,如果是在主线程调用的start方法,那么则会在主线程中执行任务。
下面的示例代码中,在当前线程中串行执行两个任务。
//调用operation的start方法,在当前线程中串行执行,无队列
- (void) executeInCurrentThread {
NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task1-----%@", [NSThread currentThread]);
}];
NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2-----%@", [NSThread currentThread]);
}];
//调用start方法,会在当前线程中串行执行
[task1 start];
[task2 start];
}
运行结果:
- 在队列中执行任务
当把任务放到队列(NSOperationQueue类)中进行执行时,系统会自动并行执行所有任务,对于开多少条线程之类的事务,完全交由系统处理,开发者只要把任务添加到队列中即可。另外,可以通过设置队列的maxConcurrentOperationCount
属性,来设置并行执行任务的数量。
下方的示例代码中,在一个队列中插入了5个任务,这5个任务并行执行。系统会同时开启多个线程,多个任务并行执行。另外,maxConcurrentOperationCount
决定了“并发”任务的数量,而不是创建线程的数量。即便设置为1,不同的任务也有可能在不同的线程中执行。
- (void) executeInQueue {
NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task1-----%@", [NSThread currentThread]);
}];
NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2-----%@", [NSThread currentThread]);
}];
NSBlockOperation *task3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task3-----%@", [NSThread currentThread]);
}];
NSBlockOperation *task4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task4-----%@", [NSThread currentThread]);
}];
NSBlockOperation *task5 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task5-----%@", [NSThread currentThread]);
}];
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//设置队列属性
queue.maxConcurrentOperationCount = 5;
//添加任务到队列
[queue addOperation:task1];
[queue addOperation:task2];
[queue addOperation:task3];
[queue addOperation:task4];
[queue addOperation:task5];
}
运行结果如图所示。可见系统同时执行5个任务,执行的顺序是不确定的,并且创建了5条线程。
- (void)addExecutionBlock:(void (^)(void))block;
下面的示例代码中在任务中添加新任务,所有的任务都会并行执行。
- (void) addTaskInOperation {
NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task1-----%@", [NSThread currentThread]);
}];
NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2-----%@", [NSThread currentThread]);
}];
//task1中添加task
[task1 addExecutionBlock:^{
NSLog(@"task1 add task-----%@", [NSThread currentThread]);
}];
//task2中添加task
[task2 addExecutionBlock:^{
NSLog(@"task2 add task-----%@", [NSThread currentThread]);
}];
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//添加任务到队列
[queue addOperation:task1];
[queue addOperation:task2];
}
运行结果如图:
- 在队列中直接添加任务
通过调用NSOperationQueue的addOperationWithBlock
方法,可以向队列中直接添加任务Block。
- (void) addTaskInQueue {
NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task1-----%@", [NSThread currentThread]);
}];
NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2-----%@", [NSThread currentThread]);
}];
//task1中添加task
[task1 addExecutionBlock:^{
NSLog(@"task1 add task-----%@", [NSThread currentThread]);
}];
//task2中添加task
[task2 addExecutionBlock:^{
NSLog(@"task2 add task-----%@", [NSThread currentThread]);
}];
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//添加任务到队列
[queue addOperation:task1];
[queue addOperation:task2];
//在queue中添加任务
[queue addOperationWithBlock:^{
NSLog(@"queue task-----%@", [NSThread currentThread]);
}];
}
如下,向队列queue中直接添加任务,也是并行执行的。
- 在任务中创建completionBlock
在NSOperation类中,可以通过设置completionBlock
来创建所有任务执行完成后,自动调用的一个Block。该Block的执行是在任务执行后被调用的,有先后顺序。
- (void) addCompletionBlock {
NSBlockOperation *task1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task1-----%@", [NSThread currentThread]);
}];
NSBlockOperation *task2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task2-----%@", [NSThread currentThread]);
}];
//task1中添加task
[task1 addExecutionBlock:^{
NSLog(@"task1 add task-----%@", [NSThread currentThread]);
}];
task1.completionBlock = ^{
NSLog(@"task1 end!!! %@", [NSThread currentThread]);
};
//task2中添加task
[task2 addExecutionBlock:^{
NSLog(@"task2 add task-----%@", [NSThread currentThread]);
}];
task2.completionBlock = ^{
NSLog(@"task2 end!!! %@", [NSThread currentThread]);
};
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//添加任务到队列
[queue addOperation:task1];
[queue addOperation:task2];
}
结果如图:
- NSOperation与GCD的区别
NSOperation与GCD都是苹果公司提供的用于多线程操作的技术,但是在使用过程中也稍有区别。
GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objective-C的对象。在GCD中,在队列中执行的是由Block构成的任务,这是一个轻量级的数据结构;而NSOperation作为一个对象,提供了更多的选择。
- 在NSOperationQueue中,可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的Block(其实是有的,但需要许多复杂的代码)。
- NSOperation能够方便地设置依赖关系,可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行。而对于GCD,没有办法设置依赖关系,
但是可以通过dispatch_barrier_async来实现这种效果
。 - KVO可以应用在NSOperation中,可以监听一个Operation是否完成或取消,这样能比GCD更加有效地掌控执行的后台任务。
- 在NSOperation中,能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行。而在GCD中,只能区分不同任务队列的优先级,如果要区分Block任务的优先级,也需要大量的复杂代码。
- 用户能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将Block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。
- 效率上:直接使用GCD确实会更高效,而NSOperation会多一些开销,但是相比起上面这么多的优势,这使得使用NSOperation仍然是很划算的选择,具体怎么选择要看我们自己。
NSOperation、NSOperationQueue 常用属性和方法归纳
NSOperation 常用属性和方法
1.取消操作方法
- (void)cancel;
可取消操作,实质是标记 isCancelled 状态
2.判断操作状态方法
- (BOOL)isFinished;
判断操作是否已经结束
- (BOOL)isCancelled;
判断操作是否已经标记为取消
- (BOOL)isExecuting;
判断操作是否正在在运行
- (BOOL)isReady;
判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关
3.操作同步
- (void)waitUntilFinished;
阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步
- (void)setCompletionBlock:(void (^)(void))block;
会在当前操作执行完毕时执行 completionBlock
- (void)addDependency:(NSOperation *)op;
添加依赖,使当前操作依赖于操作 op 的完成
- (void)removeDependency:(NSOperation *)op;
移除依赖,取消当前操作对操作 op 的依赖
@property (readonly, copy) NSArray<NSOperation *> *dependencies;
在当前操作开始执行之前完成执行的所有操作对象数组
NSOperationQueue 常用属性和方法
- (void)cancelAllOperations;
可以取消队列的所有操作
- (BOOL)isSuspended;
判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态
- (void)setSuspended:(BOOL)b;
可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列
2.操作同步
- (void)waitUntilAllOperationsAreFinished;
阻塞当前线程,直到队列中的操作全部执行完毕
3.添加/获取操作
- (void)addOperationWithBlock:(void (^)(void))block;
向队列中添加一个 NSBlockOperation 类型操作对象
- (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;
向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
- (NSArray *)operations;
当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)
- (NSUInteger)operationCount;
当前队列中的操作数
4.获取队列
+ (id)currentQueue;
获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil
+ (id)mainQueue;
获取主队列
小结
根据实际开发来说,GCD使用情况较多,简单高效。从编程原则上来看,应该是使用高层次的抽象,避免使用低层次的抽象。那么无疑我们应当选择NSOperation,因为复杂的任务可以自己通过NSOperation实现,日常还是GCD的天下,毕竟GCD有更高的并发和执行能力。。
原文地址:https://blog.csdn.net/zdsey/article/details/130495882
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_25448.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!