iOS多线程之3.NSOperation、NSOperationQueue

  GCD是C语言编写的API,对于习惯了面向对象的我们来说可能用起来不习惯,所以Apple又为我们提供了另一种多线程解决方案——使用NSOperation(操作,相当于GCD中的任务)、NSOperationQueue(队列)实现多线程。

1. 使用NSOperation子类封装操作

   NSOperation是一个抽象类,并不能封装操作。我们想封装操作有两种方式:
(1)使用系统提供的NSOperation的两个子类NSInvocationOperation和NSBlockOperation。
(2)自定义继承于NSOperation的子类,实现- (void)main方法。

- (void)viewDidLoad {
    [super viewDidLoad];
     
    //  利用NSInvocationOperation封装操作
    //  可以利用object传递参数
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(executeTask1:) object:@{@"key":@"value"}];
    [invocationOperation start];
    
    //  利用NSBlockOperation封装操作
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"执行操作2----%@",[NSThread currentThread]);
    }];
   
 [blockOperation start];
}

- (void)executeTask1:(id)object
{
    NSLog(@"执行操作1----%@",[NSThread currentThread]);
}

日志

操作1参数----{
    key = value;
}
执行操作1----{number = 1, name = main}
执行操作2----{number = 1, name = main}

  可以看到,NSOperation的子类如果只是封装单个操作,操作都是在当前线程执行的,并没有开启新的线程。
  下面看一下自定义NSOperation子类的使用,以下载图片为例。新建一个CustomOperation类,继承于NSOperation。
  CustomOperation.h

#import 
#import 

@interface CustomOperation : NSOperation

// 传入URL
@property (nonatomic, copy) NSString *strURL;
// 下载完成图片的回调
@property (nonatomic, copy) void (^downLoadImageBLock)(UIImage *image);

@end

  CustomOperation.m

#import "CustomOperation.h"

@implementation CustomOperation

// 1.实现main方法
- (void)main {
    // 2.新建一个自动释放池
    @autoreleasepool {
        // 3.正确响应取消事件
        if (self.isCancelled)  return;
        
        NSURL *url = [NSURL URLWithString:self.strURL];
        NSData *data = [NSData dataWithContentsOfURL:url];
        if (self.isCancelled) 
        {
            url = nil;
            data = nil;
        }
        
        UIImage *image = [UIImage imageWithData:data];
        if (self.isCancelled)  
        {
            image = nil;
            return;
        }
        // 4.在主线程执行回调
        dispatch_async(dispatch_get_main_queue(), ^{
             //5. 用block传值
            if (self.downLoadImageBLock) {
                self.downLoadImageBLock(image);
            }
        });
    }
}

  下面来看看如何使用自定义的NSOperation子类。

// 点击屏幕下载图片
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    // 1.创建一个CustomOperation对象
    CustomOperation *operation = [[CustomOperation alloc] init];
    // 2.为URL赋值
    operation1.strURL = @"http://h.hiphotos.baidu.com/zhidao/pic/item/6d81800a19d8bc3ed69473cb848ba61ea8d34516.jpg";
    // 3.处理block传回来的结果
    operation1.DownLoadImageBLock = ^(UIImage *image) {
        self.imageView1.image = image;
    };

   // 开始执行操作
   [operation start];
}

2. 使用NSOperationQueue实现多线程并发

  利用 NSOperationQueue和NSOperation配合使用实现多线程并发的效果只需要两步:
(1)利用NSOperation的子类封装操作。
(2)把操作放入队列中(NSOperationQueue对象)中。

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 封装操作
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作1开始执行");
        // 模拟耗时操作
        sleep(3);
        NSLog(@"执行操作1------%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作2开始执行");
        sleep(1);
        NSLog(@"执行操作2------%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作3开始执行");
        sleep(7);
        NSLog(@"执行操作3------%@",[NSThread currentThread]);
    }];

    // 把操作放入队列中
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
}

日志

操作1开始执行
操作2开始执行
操作3开始执行
执行操作2------{number = 3, name = (null)}
执行操作1------{number = 4, name = (null)}
执行操作3------{number = 5, name = (null)}

  本次在队列中添加了3个操作,系统新创建了3个线程,3个操作并发执行。需要注意的是并不是在队列中添加几个操作,系统就创建几个线程,这是由CPU的使用情况决定。另外,队列里的操作都不会在当前线程中执行

3.使用maxConcurrentOperationCount控制并发数量和实现串行

  另外我们可以决定使用maxConcurrentOperationCount并发执行的操作个数。`queue.maxConcurrentOperationCount = 2;就是两个操作同时开始执行。

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    //  设置最大并发数
    queue.maxConcurrentOperationCount = 1;
    
    // 封装操作
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作1开始执行");
        // 模拟耗时操作
        sleep(3);
        NSLog(@"执行操作1------%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作2开始执行");
        sleep(1);
        NSLog(@"执行操作2------%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作3开始执行");
        sleep(7);
        NSLog(@"执行操作3------%@",[NSThread currentThread]);
    }];

    // 把操作放入队列中
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];  
}

日志

操作2开始执行
操作1开始执行
执行操作2------{number = 3, name = (null)}
操作3开始执行
执行操作1------{number = 4, name = (null)}
执行操作3------{number = 5, name = (null)}

  注意
(1)设置并发执行的个数必须在操作放入队列中- (void)addOperation:(NSOperation *)op;前面,否则不起效果。
(2)我们并不是设置并发执行的个数为多少,就会真的有多少个操作并发执行,这只是一个期望值,如果设置的值比系统默认最大并发数大,就会以系统默认最大并发数为准。例如系统默认最大并发数为5,你设置了100,最后这个maxConcurrentOperationCount仍然是5。
(3)这个属性maxConcurrentOperationCount默认值-1,意思不做限制。
  实现串行只要设置queue.maxConcurrentOperationCount = 1就可以了,操作就会按着FIFO的原则顺序执行。
日志

操作1开始执行
执行操作1------{number = 3, name = (null)}
操作2开始执行
执行操作2------{number = 3, name = (null)}
操作3开始执行
执行操作3------{number = 3, name = (null)}

  这里需要注意的是虽然是串行,但是并不表示他们会在同一个线程中执行,这次只是例外, 可以试试在队列里添加10000个操作。

4.控制操作的执行顺序

  如果我们想一个操作执行完了,另一个操作才能继续执行,可以添加依赖。

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    // 创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    // 封装操作
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作1开始执行");
        // 模拟耗时操作
        sleep(3);
        NSLog(@"执行操作1------%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作2开始执行");
        sleep(1);
        NSLog(@"执行操作2------%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作3开始执行");
        sleep(7);
        NSLog(@"执行操作3------%@",[NSThread currentThread]);
    }];
    
    // 为operation2添加依赖operation3,  表示operation3执行完了,operation2才能执行
    [operation2 addDependency:operation3];
    [operation1 addDependency:operation2];

    // 把操作放入队列中
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
}

日志

操作3开始执行
执行操作3------{number = 3, name = (null)}
操作2开始执行
执行操作2------{number = 3, name = (null)}
操作1开始执行
执行操作1------{number = 3, name = (null)}

5.NSOperation的其他用法

5.1 利用NSBlockOperation添加多个操作

  可以利用NSBlockOperation的- (void)addExecutionBlock:(void (^)(void))block;方法添加多个操作。

- (void)viewDidLoad {
    [super viewDidLoad];
 
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        sleep(10);
        NSLog(@"执行任务1----%@", [NSThread currentThread]);
    }];
    
    // 添加新操作
    [operation addExecutionBlock:^{
        sleep(8);
        NSLog(@"执行任务2----%@",[NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^{
        sleep(7);
        NSLog(@"执行任务3----%@",[NSThread currentThread]);
    }];
    
    [operation start];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"用户点击了");
}

日志

执行任务2----{number = 3, name = (null)}
执行任务1----{number = 1, name = main}
执行任务3----{number = 3, name = (null)}
用户点击了

  这里需要注意的是NSOperation会把部分操作放到当前线程里执行。如果当前线程是主线程并且是耗时操作,会堵塞主线程,影响用户交互。

5.2 执行完的回调

   NSOperation还可以实现CGD group-notify的功能。

// 当operation里所有的操作执行完了执行block里面的代码
    operation.completionBlock = ^{
        NSLog(@"所有的操作都执行完了");
    };
5.3 设置优先级

   可以使用@property NSOperationQueuePriority queuePriority;设置NSOperation的优先级,默认是NSOperationQueuePriorityNormal`。优先级一共有五个值:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,// 优先级很低
    NSOperationQueuePriorityLow = -4L,// 优先级低
    NSOperationQueuePriorityNormal = 0,// 默认
    NSOperationQueuePriorityHigh = 4,// 优先级高
    NSOperationQueuePriorityVeryHigh = 8// 优先级很高
};
5.4 操作的取消、执行、完成

NSOperation的三个属性cancelled、executing、finished,分别就是取消,执行,完成。这三个属性都是只读的,我们通过这三个属性可以判断NSOperation的状态,是否取消了,是否正在执行,是否已经完成了。

6.NSOperationQueue的其他用法

1)暂停:
[queue setSuspended:YES];
2)恢复:
[queue setSuspended:NO];
3)判读队列的当前状态:
@property (getter=isSuspended) BOOL suspended;
  当你把队列暂停时,队列里的操作就不执行了。当你滑动列表时,可以先把队列(队列里执行下载图片的操作,列表里的cell上有图片)暂停,不滑动时再恢复,增加APP的流畅性。
4)取消所有操作
- (void)cancelAllOperations;
  队列里的所有操作都不执行了。
  以上就是关于NSOperation的常用操作,用这些基本上就能够满足我们的需求。如果还满足不了怎么办,自定义NSOperation,下一篇文章讲自定义NSOperation。

你可能感兴趣的:(iOS多线程之3.NSOperation、NSOperationQueue)