美文网首页ios专题
多任务处理-NSOperation

多任务处理-NSOperation

作者: Saxon_Geoffrey | 来源:发表于2015-03-03 01:14 被阅读296次

    多任务处理,也称作多线程处理。iOS 中主要是 NSOperation 和 GCD。这篇文章总结下 NSOperation 的一些使用方法。

    什么是 NSOperation ?

    定义

    NSOperation 表示的是一个操作。它本身是抽象基类,因此必须使用它的子类,使用NSOperation子类的方式有2种:

    • Foundation框架提供的两个具体子类: NSInvocationOperation和 NSBlockOperation

    • 自定义子类继承NSOperation,实现内部相应的方法:

      1.重写“main”方法
      
      2.在“main”方法中创建一个“autoreleasepool”
      
      3.将你的代码放在“autoreleasepool”中(ARC中也需要放在autoreleasepool中,这是为了兼容MRC代码)
      
      4.执行操作
      

    开始操作

    NSOperation 调用 start 方法即可开始执行操作,NSOperation 对象默认按同步方式执行,也就是在调用start方法的那个线程中直接执行。isConcurrent方法默认返回NO,表示操作与调用线程同步执行。

    - (void)createSynchronouslyOperations {
        NSNumber *simpleObject = @123;
        NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationEntry:) object:simpleObject];
        [operation start];
    }
    
    -(void)operationEntry:(id)paramObject{
        NSLog(@"***Parameter Object = %@",paramObject);
        NSLog(@"***Main Thread = %@",[NSThread mainThread]);
        NSLog(@"***current Thread = %@",[NSThread currentThread]);
    }
    

    如果要使NSOperation对象按异步方式执行,需要把它加入到NSOperationQueue,举个例子,我们使用NSInvocationOperation 完成异步的图片下载功能:

    - (void)createAsynchronouslyOperations{
        NSURL *url = [NSURL URLWithString:@"xxx"];
        NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self
                                                                               selector:@selector(downloadImage:)
                                                                                 object:url];
        [operation setCompletionBlock:^() {
            NSLog(@"下载完成");
        }];
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        [queue addOperation:operation];
    }
    
    //异步方式下载图片
    -(void)downloadImage:(NSString *)url{
        UIImage * image = [[UIImage alloc]initWithData:[[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:url]]];
        [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
    }
    
    //下载完成后,切换到主线程更新UI
    -(void)updateUI:(UIImage*) image{
        self.imageView.image = image;
    }
    

    取消操作

    operation开始执行之后, 我们可以调用cancel方法中途取消操作:

    [operation cancel];  
    

    监听操作的执行

    每当一个NSOperation执行完毕,它就会调用自身的 completionBlock 属性一次,这然后你就可以在这个 completionBlock 中加入自己的代码逻辑。比如说,你可以在一个网络请求操作的completionBlock来处理操作执行完以后从服务器下载下来的数据。

    [operation setCompletionBlock:^() {  
        NSLog(@"下载完成");  
    }]; 
    

    NSBlockOperation

    该子类和NSInvocationOperation不同的是,它能够并发地执行一个或多个block对象,所有相关的block都执行完之后,操作才算完成

    -(void)BlockOperation{
        NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
            NSLog(@"1,线程:%@", [NSThread currentThread]);
        }];
        
        [operation addExecutionBlock:^() {
            NSLog(@"2,线程:%@", [NSThread currentThread]);
        }];
        
        [operation addExecutionBlock:^() {
            NSLog(@"3,线程:%@", [NSThread currentThread]);
        }];
        
        [operation addExecutionBlock:^() {
            NSLog(@"4,线程:%@", [NSThread currentThread]);
        }];  
        
        // 开始执行任务  
        [operation start];
    }
    

    打印信息如下

    4,线程:<NSThread: 0x7fca4873cea0>{number = 7, name = (null)}
    1,线程:<NSThread: 0x7fca4861dbc0>{number = 1, name = main}
    2,线程:<NSThread: 0x7fca48652850>{number = 5, name = (null)}
    3,线程:<NSThread: 0x7fca48522db0>{number = 6, name = (null)}
    

    number属性可以看成是线程的id,可以看出,这4个block是并发执行的。

    自定义NSOperation

    如果 NSInvocationOperation 和 NSBlockOperation 对象不能满足普通的需求, 你可以直接继承 NSOperation ,并添加额外的操作。具体方法是通过重写 main 或者 star t方法来定义自己的 operations 。前一种方法非常简单,开发者不需要管理一些状态属性(例如 isExecuting 和 isFinished ),当main方法返回的时候,这个operation就结束了。这种方式使用起来非常简单,但是灵活性相对重写start来说要少一些。

    我们新建一个DownloadImageOperation,并使用delegate来完成一个回调。

    DownloadImageOperation.h

    @protocol DownloadOperationDelegate;
    @interface DownloadImageOperation : NSOperation
    @property (nonatomic, copy) NSString *imageUrl;
    @property (nonatomic, retain) id<DownloadOperationDelegate> delegate;
    - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate;
    @end
    
    @protocol DownloadOperationDelegate <NSObject>
    - (void)downloadFinishWithImage:(UIImage *)image;
    @end
    

    为了能使用操作队列所提供的取消功能,每一步操作都需要检查 isCancelled 属性。如果为 true,则直接 return 函数。
    DownloadImageOperation.m

    @implementation DownloadImageOperation
    
    // 初始化
    - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate {
        if (self = [super init]) {
            _imageUrl = url;
            _delegate = delegate;
        }
        return self;
    }
    
    // 执行主任务
    - (void)main {
        // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
        @autoreleasepool {
            //NSOperation的-cancel状态调用时,会通过KVO通知isCancelled的keyPath来修改isCancelled属性的返回值
            if (self.isCancelled) return;
            
            NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:self.imageUrl]];
            
            if (self.isCancelled) {
                imageData = nil;
                return;
            }
            // 初始化图片
            UIImage *image = [UIImage imageWithData:imageData];
            if (self.isCancelled) {
                image = nil;
                return;
            }
            if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) {
                // 把图片数据传回到主线程
                [(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO];
            }
        }
    }
    
    @end
    

    我们新建一个控制器来使用这个DownloadImageOperation

    @interface DownloadImageOperationController ()<DownloadOperationDelegate>
    @property (weak, nonatomic) IBOutlet UIImageView *image;
    @end
    
    @implementation DownloadImageOperationController
    
    - (void)viewDidLoad{
        [super viewDidLoad];
        //加入到 NSOperationQueue 中
        NSOperationQueue *queue = [[NSOperationQueue alloc]init];
        DownloadImageOperation *customOperation = [[DownloadImageOperation alloc]initWithUrl:kURL delegate:self];
        [queue addOperation:customOperation];
    }
    
    -(void)downloadFinishWithImage:(UIImage *)image{
        self.image.image = image;
    }
    
    @end
    

    如果你希望拥有更多的控制权,以及在一个操作中可以执行异步任务,那么就重写 start 方法:

    @implementation CustomOperation
    - (void)start{
         _isExecuting = YES;
         _isFinished = NO;
         // 开始处理,在结束时应该调用 finished ...
    }
    
    - (void)finished{
         _isExecuting = NO;
         _isFinished = YES;
    }
    @end
    

    上面讲过,如果要使NSOperation对象按异步方式执行,需要把它加入到NSOperationQueue。NSOperationQueue 有两种不同类型的队列:主队列和自定义队列。为了方便理解,也可以称作主队列和非主队列。主队列运行在主线程之上,而非主队列在后台执行。如上所示,在两种类型中,这些队列所处理的任务都使用NSOperation 的子类来表述。

    NSOperationQueue

    创建

    //后台线程依据

    NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
    

    //主线程

    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    

    添加operation

    //添加一个operation
    [queue addOperation:operation]; 
    //添加一组operation
    [queue addOperations:operations waitUntilFinished:NO];
    //将 block 添加到操作队列中。有时候会非常的方便,比如你希望在主队列中调度一个一次性任务:
    [queue addOperationWithBlock:^() {  
        NSLog(@"执行一个新的操作,线程:%@", [NSThread currentThread]);  
    }]; 
    

    NSOperation添加到queue之后,绝对不要再修改NSOperation对象的状态。因为NSOperation对象可能会在任何时候运行,因此改变NSOperation对象的依赖或数据会产生不利的影响。你只能查看NSOperation对象的状态, 比如是否正在运行、等待运行、已经完成等

    设置队列的最大并发操作数量

    通过maxConcurrentOperationCount属性可以控制一个特定队列中可以有多少个操作参与并发执行。将其设置为1的话,你将得到一个串行队列,这在以隔离为目的的时候会很有用。不过operation执行的顺序仍然依赖于其它因素,比如operation是否准备好和operation的优先级等。因此串行化的operation queue并不等同于GCD

    [queue setMaxConcurrentOperationCount:1]; 
    

    队列中operation的优先级

    优先级等级则是operation对象本身的一个属性。默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级。这5个优先级包括:

    • NSOperationQueuePriorityVeryHigh
    • NSOperationQueuePriorityHigh
    • NSOperationQueuePriorityNormal
    • NSOperationQueuePriorityLow
    • NSOperationQueuePriorityVeryLow

    优先级只能应用于相同 queue 中的 operations 。如果应用有多个 operation queue, 每个queue的优先级等级是互相独立的。因此不同 queue 中的低优先级操作仍然可能比高优先级操作更早执行。

    依赖关系

    如果你需要进一步在除了5个标准的优先级以外对operation的执行顺序进行控制的话,还可以在operation之间指定依赖关系,当某个NSOperation对象依赖于其它NSOperation对象的完成时,就可以通过addDependency方法添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。

    看下面的例子:

    //在某个任务完成后执行特定的任务
    -(void)firstOperationEntry:(id)paramObject{
        NSLog(@"***Parameter Object = %@",paramObject);
        NSLog(@"***Main Thread = %@",[NSThread mainThread]);
        NSLog(@"***current Thread = %@",[NSThread currentThread]);
    }
    
    -(void)secondOperationEntry:(id)paramObject{
        NSLog(@"%s",__FUNCTION__);
        NSLog(@"***Parameter Object = %@",paramObject);
        NSLog(@"***Main Thread = %@",[NSThread mainThread]);
        NSLog(@"***current Thread = %@",[NSThread currentThread]);
    }
    
    -(void)DependencyOperation{
        NSString *firstNumber = @"1";
        NSString *secondNumber = @"2";
        NSInvocationOperation *firstOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(firstOperationEntry:) object:firstNumber];
        NSInvocationOperation *secondOperation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(secondOperationEntry:) object:secondNumber];
        [firstOperation addDependency:secondOperation];//与之相反的是removeDependency
        NSOperationQueue *operationQueue = [[NSOperationQueue alloc]init];
        [operationQueue addOperation:firstOperation];
        [operationQueue addOperation:secondOperation];
        
        NSLog(@"******Main Thread is here");
    }
    

    打印结果

    ******Main Thread is here
    ***Parameter Object = 2
    ***Main Thread = <NSThread: 0x7fd32af02e80>{number = 1, name = (null)}
    ***current Thread = <NSThread: 0x7fd32aeb5fc0>{number = 5, name = (null)}
    ***Parameter Object = 1
    ***Main Thread = <NSThread: 0x7fd32af02e80>{number = 1, name = (null)}
    ***current Thread = <NSThread: 0x7fd32aeb5fc0>{number = 5, name = (null)}
    

    可以看出,只有secondOperation都已经完成操作,firstOperation才会开始执行操作
    依赖关系不局限于相同queue中的NSOperation对象,NSOperation对象会管理自己的依赖, 因此完全可以在不同的queue之间的NSOperation对象创建依赖关系。此外,确保不要创建依赖循环,像A依赖B,B又依赖A。

    暂停和继续queue

    如果你想临时暂停Operations的执行,可以使用queue的setSuspended:方法暂停queue。不过暂停一个queue不会导致正在执行的operation在任务中途暂停,只是简单地阻止调度新Operation执行。你可以在响应用户请求时,暂停一个queue来暂停等待中的任务。稍后根据用户的请求,可以再次调用setSuspended:方法继续queue中operation的执行

    // 暂停queue  
    [queue setSuspended:YES];  
      
    // 继续queue  
    [queue setSuspended:NO]; 
    

    相关文章

      网友评论

      本文标题:多任务处理-NSOperation

      本文链接:https://www.haomeiwen.com/subject/yfrmxttx.html