NSOperation NSOperationQueue

一. 简介

  除了使用NSThread和GCD可以实现多线程,配合使用NSOperation和NSOperationQueue也能实现多线程。

  使用NSOperation和NSOperationQueue实现多线程的操作步骤:

  1. 将需要执行的操作封装到NSOperation的子类对象中。

    实际上,NSOperaion是一个抽象类,并不可以封装操作,必须使用它的子类

  2. 将NSOperation对象添加到NSOperationQueue中

  3. 系统会自动将NSOperationQueue中的NSOperation取出

  4. 将取出的NSOperation封装的操作放到一个新线程中执行。

二. NSOperation的使用

  1. 实现NSOperation的封装有三种:

    (1)NSInvocationOperation

       创建NSInvocationOperation对象,调用start方法开始执行操作。

       默认情况下,调用start方法后并不会开启一个新线程去执行操作,而是在当前线程同步执行操作。

         只有将NSOperation操作放到NSOperationQueue中,才会异步执行操作。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation1:) object:nil];    
    [operation1 start];
    
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation2:) object:nil];
    [operation2 start];
}

- (void)startOperation1: (id)sender {
    NSLog(@"operation1---------%@", [NSThread currentThread]);
}

- (void)startOperation2:(id)sender {
    NSLog(@"operation2---------%@", [NSThread currentThread]);
}

// 打印结果:
2017-02-24 12:58:11.696 OperationDemo[74424:8270352] operation1---------<NSThread: 0x7a86af70>{number = 1, name = main}
2017-02-24 12:58:11.698 OperationDemo[74424:8270352] operation2---------<NSThread: 0x7a86af70>{number = 1, name = main}   
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];    
    
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation1:) object:nil];    
    [queue addOperation:operation1];
    
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation2:) object:nil];
    [queue addOperation:operation2];
}

- (void)startOperation1: (id)sender {
    NSLog(@"operation1---------%@", [NSThread currentThread]);
}

- (void)startOperation2:(id)sender {
    NSLog(@"operation2---------%@", [NSThread currentThread]);
}

// 打印结果:
2017-02-24 13:13:19.351 OperationDemo[74639:8308964] operation2---------<NSThread: 0x7b6a2140>{number = 3, name = (null)}
2017-02-24 13:13:19.351 OperationDemo[74639:8308962] operation1---------<NSThread: 0x7b8e5ee0>{number = 2, name = (null)}

    (2)NSBlockOperation

      创建NSBlockOperation对象,使用addExecutionBlock:方法添加更多的操作。

      只要NSBlockOperation封装的操作数大于1,就会异步执行操作。

 
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation1---------%@", [NSThread currentThread]);
    }];
    [operation start];

// 打印结果:
2017-02-24 13:20:39.094 OperationDemo[74785:8328635] operation1---------<NSThread: 0x78e315f0>{number = 1, name = main}
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation1---------%@", [NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^{
        NSLog(@"operation2---------%@", [NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^{
        NSLog(@"operation3---------%@", [NSThread currentThread]);
    }];
    
    [operation start];

// 打印结果:
2017-02-24 13:17:58.073 OperationDemo[74742:8321309] operation3---------<NSThread: 0x79072250>{number = 3, name = (null)}
2017-02-24 13:17:58.073 OperationDemo[74742:8321307] operation2---------<NSThread: 0x78fb8840>{number = 2, name = (null)}
2017-02-24 13:17:58.073 OperationDemo[74742:8321024] operation1---------<NSThread: 0x78f8e020>{number = 1, name = main}

    (3)自定义子类继承NSOperation,实现内部相应的main方法封装操作

      需要重写main方法,将操作任务放到main方法中。

@implementation CustomOperation

- (void)main {

    // 将操作任务放在这里
    NSLog(@"-----1-----");
}

@end

 三. NSOperationQueue

  1. NSOpeationQueue作用:

    NSOperation可以调用start方法执行任务,但默认是同步执行的。

    如果将NSOperation添加到NSOperationQueue中,系统会自动异步执行操作。

  2. 添加NSOperation到NSOperationQueue有两个方法:

    -(void)addOperation:(NSOperation *)operation;

    -(void)addOperationWithBlock:(void(^)(void))block;

    只要将一个操作添加到队列(默认是并行队列),那么队列内部会自动调用start方法。

    如果要将队列设置为串行,只需要设置队列的queue.maxConcurrentOperationCount = 1即可。

NSOperationQueue *queue = [[NSOperationQueue alloc] init];    
    
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation1:) object:nil];    
    [queue addOperation:operation1];
    
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(startOperation2:) object:nil];
    [queue addOperation:operation2];
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperationWithBlock:^{
        NSLog(@"operation1-------%@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"operation2-------%@", [NSThread currentThread]);
    }];

// 打印结果:
2017-02-24 13:30:52.198 OperationDemo[74890:8354590] operation2-------<NSThread: 0x7b6897d0>{number = 3, name = (null)}
2017-02-24 13:30:52.198 OperationDemo[74890:8354591] operation1-------<NSThread: 0x7b750aa0>{number = 2, name = (null)}

四. NSOperationQueue的串行和并发: 最大并发数

  队列的最大并发数为maxConcurrentOperationCount;

  默认情况下,maxConcurrentOperationCount = -1,代表不限制最大并发数,可以创建N多个线程;

  通过[[NSOperationQueue alloc] init] 创建的NSOperationQueue是并发的,如果想设置为串行,只需要将maxConcurrentOperationCount = 1;

  不可以将maxConcurrentOperationCount设置为0,否则任务将不会执行。

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    
    [queue addOperationWithBlock:^{
        NSLog(@"operation1-------%@", [NSThread currentThread]);
    }];
    
    [queue addOperationWithBlock:^{
        NSLog(@"operation2-------%@", [NSThread currentThread]);
    }];

//打印结果:
2017-02-24 13:36:23.018 OperationDemo[74951:8368501] operation1-------<NSThread: 0xa22dc70>{number = 2, name = (null)}
2017-02-24 13:36:23.019 OperationDemo[74951:8368501] operation2-------<NSThread: 0xa22dc70>{number = 2, name = (null)}

 五. NSOperationQueue的取消、暂停、恢复

  可以通过NSOperation的cancel方法来取消单个操作。

  操作只要取消,就不会再恢复。

  取消任务和暂停任务一样,不会取消当前正在执行的操作,职能取消还未执行的操作。

  可以通过NSOperationQueue的cancelAllOperations方法来取消所有的操作。

  暂停和恢复任务:

    -(void)setSuspended:(Bool)suspend;   YES表示暂停,NO表示恢复。

    -(Bool)isSuspended;  判断队列是否在暂停中

  若要取消的任务是自定义的操作队列的话,执行完一个耗时操作后,需要增加是否取消任务的判断,再去执行另外一个操作任务。

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation1------%@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation2------%@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"operation3------%@", [NSThread currentThread]);
    }];
    
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    
    [operation3 cancel];

// 打印结果:
2017-02-24 13:43:43.052 OperationDemo[75069:8388603] operation1------<NSThread: 0x79466810>{number = 2, name = (null)}
2017-02-24 13:43:43.054 OperationDemo[75069:8388603] operation2------<NSThread: 0x79466810>{number = 2, name = (null)}
@implementation CustomOperation

- (void)main {

    for (int i = 0; i < 10000; i ++) {
        NSLog(@"-----1-----%@-----", [NSThread currentThread]);
    }
    
    // 增加队列是否取消任务的判断
    if (self.isCancelled) {
        return;
    }
    
    for (int i = 0; i < 10000; i ++) {
        NSLog(@"-----2-----%@-----", [NSThread currentThread]);
    }
    
    // 增加队列是否取消任务的判断
    if (self.isCancelled) {
        return;
    }
    
    for (int i = 0; i < 10000; i ++) {
        NSLog(@"-----3-----%@-----", [NSThread currentThread]);
    }
    
    // 增加队列是否取消任务的判断
    if (self.isCancelled) {
        return;
    }
    
    for (int i = 0; i < 10000; i ++) {
        NSLog(@"-----4-----%@-----", [NSThread currentThread]);
    }
}

@end

 

六. NSOperationQueue线程间通信

  开启子线程下载图片,下载完成后,在主线程更新UI。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    
    [queue addOperationWithBlock:^{
        NSString *urlString = @"http://www.wallcoo.com/animal/Dogs_Summer_and_Winter/wallpapers/1920x1200/DogsB10_Lucy.jpg";
        NSURL *url = [NSURL URLWithString:urlString];
        NSData *data = [NSData dataWithContentsOfURL:url];
        if (data) {
            UIImage *image = [UIImage imageWithData:data];
            
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
               
                NSLog(@"更新UI");
                _backImageView.image = image;
                
            }];
        }
    }];  
}

 七. 操作依赖

  可以在同一个queue中添加操作依赖,也可以在不痛的队列中添加操作依赖。

  注意:千万不要A依赖B,同时B也依赖A。

  经典实例:合成图片

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    // 队列1用于下载图片
    NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
    // 队列2用于合成图片
    NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
    
    __block UIImage *image1 = nil;
    __block UIImage *image2 = nil;
    
    // 下载图片1
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSString *urlString = @"http://www.wallcoo.com/animal/Dogs_Summer_and_Winter/wallpapers/1920x1200/DogsB10_Lucy.jpg";
        NSURL *url = [NSURL URLWithString:urlString];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        image1 = image;
    }];
    
    // 下载图片2
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSString *urlString = @"http://img4.duitang.com/uploads/item/201507/30/20150730163204_A24MX.thumb.700_0.jpeg";
        NSURL *url = [NSURL URLWithString:urlString];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        image2 = image;
    }];
    
    [operation1 setCompletionBlock:^{
        NSLog(@"图片1下载完成");
    }];
    
    [operation2 setCompletionBlock:^{
        NSLog(@"图片2下载完成");
    }];
    
    // 合成图片
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
        [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        
        // 主线程更新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"主线程更新UI");
            _backImageView.image = newImage;
        }];
        
    }];
    
    // 合成图片依赖于下载图片---只有图片下载完成,才可以进行合成操作
    [operation3 addDependency:operation1];
    [operation3 addDependency:operation2];    
    
    // 将下载图片添加到队列1中
    [queue1 addOperation:operation1];
    [queue1 addOperation:operation2];
    
    // 将合成图片添加到队列2中
    [queue2 addOperation:operation3];    
}

 摘自:iOS NSOperation(http://www.jianshu.com/p/adb075114246)

posted @ 2017-02-24 13:21  紫洁  阅读(238)  评论(0编辑  收藏  举报