在上一篇中谈了GCD的使用,其实在iOS中,苹果还为我们提供了另一种实现多线程的方案---NSOperation,配合使用 NSOperation 和NSOperationQueue可以做一些CGD不太好实现的操作,如“最大并发数”的控制。
NSOperation和NSOperationQueue实现多线程的具体步骤:
先将需要执行的操作封装到一个NSOperation对象中
然后将NSOperation对象添加到NSOperationQueue中
系统会自动将NSOperationQueue中的NSOperation取出来
将取出的NSOperation封装的操作放到一条新线程中执行
1. NSOperation及其子类
(1)NSOperation是个抽象类,并不具备封装操作的能力,必须使用它的子类
(2)使用NSOperation子类的方式有3种
NSInvocationOperation
NSBlockOperation
自定义子类继承NSOperation,实现内部相应的方法
2. 基本使用
2.1 单个NSInvocationOperation的使用
// 1. 创建操作
NSOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"Invocation"];
// 2. 默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作
// [op start];
// 2. 放到队列
NSOperationQueue *q = [[NSOperationQueue alloc] init];
// 只有将NSOperation放到一个NSOperationQueue中,才会异步执行操作
[q addOperation:op];
2.2 多个NSInvocationOperation的使用
// 队列 (GCD里面的并发(全局)队列使用最多。所以NSOperation技术直接把GCD里面的并发队列封装起来)
// NSOperationQueue队列,本质就是GCD里面的并发队列
// 操作就是GCD里面异步执行的任务
NSOperationQueue *q = [[NSOperationQueue alloc] init];
// 把多个操作放到队列
for (int i = 0 ; i < 10; i++)
{
NSOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"Invocation"];
// 把block操作放到队列
[q addOperation:op];
}
2.3 NSBlockOperation的使用
// 相当于GCD的并发队列
NSOperationQueue *q = [[NSOperationQueue alloc] init];
// 主队列(跟GCD里的主队列一样)
// NSOperationQueue *q = [NSOperationQueue mainQueue];
// 多个操作
for (int i = 0; i < 10; i++) {
NSBlockOperation * op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@---%d", [NSThread currentThread], i);
}];
// 把block操作放到队列
[q addOperation:op];
}
创建NSBlockOperation对象
+ (id)blockOperationWithBlock:(void (^)(void))block;
通过addExecutionBlock:方法添加更多的操作
- (void)addExecutionBlock:(void (^)(void))block;
注意:只要NSBlockOperation封装的操作数 > 1,就会异步执行操作
// 队列
NSOperationQueue *q = [[NSOperationQueue alloc] init];
for (int i = 0; i < 10; i++) {
// 不创建操作对象,使用addOperationWithBlock:直接添加操作到队列
[q addOperationWithBlock:^{
NSLog(@"%@---%d", [NSThread currentThread], i);
}];
}
// 创建并添加一个 NSBlockOperation
NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"op1 --- %@", [NSThread currentThread]);
}];
[op1 addExecutionBlock:^{
NSLog(@"op1-1");
}];
[q addOperation:op1];
// 创建并添加一个 NSInvocationOperation
NSOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage:) object:@"Invocation"];
[q addOperation:op2];
2.4 线程间通信
NSOperationQueue *q = [[NSOperationQueue alloc] init];
[q addOperationWithBlock:^{
NSLog(@"耗时操作....%@", [NSThread currentThread]);
// 在主线程更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"更新UI....%@", [NSThread currentThread]);
}];
}];
2.5 设置最大并发数
/**负责调度所有的操作*/
@property(nonatomic, strong) NSOperationQueue *opQueue;
// 懒加载的方式,初始化NSOperationQueue对象
- (NSOperationQueue *)opQueue
{
if (_opQueue == nil) {
_opQueue = [[NSOperationQueue alloc] init];
}
return _opQueue;
}
// 设置最大的并发数是2 (最大并发数,不是线程的数量。 而是同时执行的操作的数量)
self.opQueue.maxConcurrentOperationCount = 2;
for (int i = 0; i < 20; i++) {
NSOperation *op = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:1.0];
NSLog(@"%@---%d", [NSThread currentThread], i);
}];
[self.opQueue addOperation:op];
}
2.6 暂停/继续 (对队列的暂停和继续)
// 判断操作的数量,当前队列里面是不是有操作
if (self.opQueue.operationCount == 0) {
NSLog(@"没有操作");
return;
}
// 暂停继续
self.opQueue.suspended = !self.opQueue.suspended;
if (self.opQueue.suspended) { // 队列的挂起以后,队列里面的操作还在
NSLog(@"暂停");
}else{
NSLog(@"继续");
}
2.7 取消队列里的所有操作
“取消操作,并不会影响队列的挂起状态”
// 取消队列的所有操作
[self.opQueue cancelAllOperations]; // 取消队列的所有操作,会把任务从队列里面全部删除
NSLog(@"取消所有的操作");
// 取消队列的挂起状态
// (只要是取消了队列的操作,我们就把队列处于启动状态。以便于队列的继续)
self.opQueue.suspended = NO;
2.7 依赖关系dependency
/**
例子:
1. 下载一个小说的压缩包
2. 解压缩,删除压缩包
3. 更新UI
*/
NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"1. 下载一个小说的压缩包, %@",[NSThread currentThread]);
}];
NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"2. 解压缩,删除压缩包, %@",[NSThread currentThread]);
}];
NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"3. 更新UI, %@",[NSThread currentThread]);
}];
// 指定任务之间的依赖关系 -- 依赖关系可以跨队列(可以在子线程下载完,到主线程更新UI)
[op2 addDependency:op1];
[op3 addDependency:op2];
// 注意点:一定不要出现循环依赖关系
// [op1 addDependency:op3];
// waitUntilFinished 类似GCD的调度组的通知
// NO 不等待,会直接执行 NSLog(@"come here");
// YES 等待上面的操作执行结束,再 执行 NSLog(@"come here")
[self.opQueue addOperations:@[op1, op2] waitUntilFinished:YES];
// 在主线程更新UI
[[NSOperationQueue mainQueue] addOperation:op3];
NSLog(@"come here");