NSOperation

作者: SamCheck | 来源:发表于2018-06-03 14:07 被阅读2次

    概述

    iOS并发编程中,把每个并发任务定义为一个Operation。NSOperation是一个抽象类,无法直接使用,它只定义了Operation的一些基本方法。我们需要创建一个继承于它的子类或者使用系统预定义的子类。

    方法结构

    NSOperation

    把逻辑代码写在NSOperation中,就是把逻辑代码添加一层壳,执行NSOperation就是间接的执行逻辑代码。

    • 用来定义操作对象的基础(抽象)类。处理并发任务时,具体子类通常要重写main、isConcurrent、isExecuting 、isFinished方法。
    • Operation默认都是串行操作(FIFO),默认情况下Operation并不额外创建线程。
    • 启动一个Operation任务
      如果希望拥有更多的控制权,以及在一个操作中可以执行异步任务,那么就重写 start 方法。
      如果重写 start 方法,你必须手动管理操作的状态。 为了让操作队列能够捕获到操作的改变,需要将状态的属性以配合 KVO 的方式进行实现。
    - (void)start;
    
    • 取消一个Operation
      如果你在main方法中没有对cancel进行任何处理的话,发送cancel消息是没有任何效果的。为了让Operation响应cancel消息,那么你就要在main方法中一些适当的地方手动的判断isCancelled属性,如果返回YES的话,应释放相关资源并立刻停止继续执行。
    @property (readonly, getter=isCancelled) BOOL cancelled;
    - (void)cancel;
    
    • 用来执行你所想要执行的任务
      可以通过重写 main 方法 来定义自己的 operations,开发者不需要管理一些状态属性(例如 isExecuting 和 isFinished),当 main 方法返回的时候,这个 operation 就结束了。
    - (void)main;
    
    • 判断Operation是否是可并发的
    @property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below
    @property (readonly, getter=isAsynchronous) BOOL asynchronous API_AVAILABLE(macos(10.8), ios(7.0), watchos(2.0), tvos(9.0));
    
    • 查看、添加、删除 操作依赖
    @property (readonly, copy) NSArray<NSOperation *> *dependencies;
    - (void)addDependency:(NSOperation *)op;
    - (void)removeDependency:(NSOperation *)op;
    

    下面就是intermediateOperation操作必须等到operation1operation2 完成后才能执行。

    [intermediateOperation addDependency:operation1];
    [intermediateOperation addDependency:operation2];
    
    • Operation执行完成时自动执行completionBlock。可以在此进行一些完成的处理。
      每个Operation都可以设置一个completionBlock。
    @property (nullable, copy) void (^completionBlock)(void) API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    
    • 线程优先级
    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
        NSOperationQueuePriorityVeryLow = -8L,
        NSOperationQueuePriorityLow = -4L,
        NSOperationQueuePriorityNormal = 0,
        NSOperationQueuePriorityHigh = 4,
        NSOperationQueuePriorityVeryHigh = 8
    };
    
    @property NSOperationQueuePriority queuePriority;
    
    • 可以通过KVO监听Operation的一下状态改变的Key
    isCancelled
    isConcurrent
    isExecuting
    isFinished
    isReady
    dependencies
    queuePriority
    completionBlock
    

    NSInvocationOperation

    • 初始化:
    - (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;
    
    - (instancetype)initWithInvocation:(NSInvocation *)inv;
    
    • 变量
    @property (readonly, retain) NSInvocation *invocation;
    //个Operation完成后返回结果
    @property (nullable, readonly, retain) id result;
    

    NSBlockOperation

    • 初始化:
    + (instancetype)blockOperationWithBlock:(void (^)(void))block;
    
    - (void)addExecutionBlock:(void (^)(void))block;
    
    @property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;
    

    Operation操作流程

    一些公共方法:

    -(void)operationComplete {
        NSLog(@"All task finished.");
    }
    
    -(void)logOperation:(NSOperation *)op keyPathes:(NSArray *)keyPathes {
        [keyPathes enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSLog(@"%@ %@ = %@",op.name,obj,[[op valueForKey:obj] boolValue]?@"YES":@"NO");
        }];
    }
    
    -(void)addObserverForOperation:(id)op keyPathes:(NSArray *)keyPathes {
        [keyPathes enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [op addObserver:self forKeyPath:obj options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:NULL];
        }];
    }
    
    -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
        if ([object isKindOfClass:[NSOperation class]]) {
            NSLog(@"observeValueForKeyPath:%@---%@---%@",[object name],keyPath,change);
        }
    }
    
    • 先执行start,后执行cancel
    -(void)operationKeysChange{
        TestBlockOperation * op = [TestBlockOperation blockOperationWithBlock:^{
            NSLog(@"enter op");
            [NSThread sleepForTimeInterval:3];
            NSLog(@"leave op");
        }];
        op.name = @"op";
        NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
        [self logOperation:op keyPathes:keyPathes];
        [self addObserverForOperation:op keyPathes:keyPathes];
    op.completionBlock = ^{
            [self operationComplete];
        };
        [op start];
        [op cancel];
    }
    

    初始状态下,ready为YES,其他均为NO。
    当我们调用 -start 后,执行 -main 之前 isExecuting 属性从NO被置为YES。
    调用 -main 之后开始执行提交到Operation中的任务。
    任务完成后 isExecuting 属性从YES被置为NO,isFinished 属性从NO被置为YES。

    op isReady = YES
    op isCancelled = NO
    op isExecuting = NO
    op isFinished = NO
    op before start
    observeValueForKeyPath:op---isExecuting---{kind = 1;new = 1;old = 0;}
    op before main
    enter bp1
    leave bp1
    /*这里执行业务代码,完成后才进行下面的操作*/
    op after main
    All task finished.
    observeValueForKeyPath:op---isExecuting---{kind = 1;new = 0;old = 1;}
    observeValueForKeyPath:op---isFinished---{kind = 1;new = 1;old = 0;}
    op after start
    op before cancel
    op after cancel
    
    • 先执行cancel,后执行start
    -(void)operationKeysChange{
        TestBlockOperation * op = [TestBlockOperation blockOperationWithBlock:^{
            NSLog(@"enter op");
            [NSThread sleepForTimeInterval:3];
            NSLog(@"leave op");
        }];
        op.name = @"op";
        NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
        [self logOperation:op keyPathes:keyPathes];
        [self addObserverForOperation:op keyPathes:keyPathes];
    op.completionBlock = ^{
            [self operationComplete];
        };
        [op cancel];
        [op start];
    }
    

    先调用 -start ,后调用 -cancel ,isCancelled 属性从NO被置为YES,isReady 属性无论什么状态都会被置为YES。
    先调用 -start ,后调用 -cancel ,会将 isFinished 属性从NO被置为YES,然后并不调用 -main 方法。

    op isReady = YES
    op isCancelled = NO
    op isExecuting = NO
    op isFinished = NO
    op before cancel
    observeValueForKeyPath:op---isCancelled---{kind = 1;new = 1;old = 0;}
    observeValueForKeyPath:op---isReady---{kind = 1;new = 1;old = 1;}
    op after cancel
    op before start
    All task finished.
    observeValueForKeyPath:op---isFinished---{kind = 1;new = 1;old = 0;}
    op after start
    
    • NSOperationQueue 但是没有依赖
    -(void) operationKeysChange {
        TestBlockOperation * bp1 = [TestBlockOperation blockOperationWithBlock:^{
            NSLog(@"enter bp1");
            [NSThread sleepForTimeInterval:3];
            NSLog(@"leave bp1");
        }];
        bp1.name = @"bp1";
        bp1.completionBlock = ^{
            NSLog(@"bp1 complete");
        };
        
        TestBlockOperation * bp2 = [TestBlockOperation blockOperationWithBlock:^{
            NSLog(@"enter bp2");
            [NSThread sleepForTimeInterval:3];
            NSLog(@"leave bp2");
        }];
        bp2.name = @"bp2";
        bp2.completionBlock = ^{
            NSLog(@"bp2 complete");
        };
        
        NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
        
        [self logOperation:bp1 keyPathes:keyPathes];
        [self logOperation:bp2 keyPathes:keyPathes];
        [self addObserverForOperation:bp1 keyPathes:keyPathes];
        [self addObserverForOperation:bp2 keyPathes:keyPathes];
        
        NSOperationQueue * q = [NSOperationQueue new];
        [q addOperation:bp1];
        [q addOperation:bp2];
    }
    

    NSOperationQueue 执行顺序和NSOperation一样

    
    bp1 isReady = YES
    bp1 isCancelled = NO
    bp1 isExecuting = NO
    bp1 isFinished = NO
    
    bp2 isReady = YES
    bp2 isCancelled = NO
    bp2 isExecuting = NO
    bp2 isFinished = NO
    
    bp1 before start
    bp2 before start
    
    observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 1;old = 0;}
    observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 1;old = 0;}
    
    bp1 before main
    bp2 before main
    
    enter bp1
    enter bp2
    
    leave bp1
    leave bp2
    
    bp2 after main
    bp1 after main
    
    bp2 complete
    bp1 complete
    
    observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 0;old = 1;}
    observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 0;old = 1;}
    
    observeValueForKeyPath:bp1---isFinished---{kind = 1;new = 1;old = 0;}
    observeValueForKeyPath:bp2---isFinished---{kind = 1;new = 1;old = 0;}
    
    bp1 after start
    bp2 after start
    
    • NSOperationQueue 但是有依赖
    -(void) operationKeysChange {
        TestBlockOperation * bp1 = [TestBlockOperation blockOperationWithBlock:^{
            NSLog(@"enter bp1");
            [NSThread sleepForTimeInterval:3];
            NSLog(@"leave bp1");
        }];
        bp1.name = @"bp1";
        bp1.completionBlock = ^{
            NSLog(@"bp1 complete");
        };
        
        TestBlockOperation * bp2 = [TestBlockOperation blockOperationWithBlock:^{
            NSLog(@"enter bp2");
            [NSThread sleepForTimeInterval:3];
            NSLog(@"leave bp2");
        }];
        bp2.name = @"bp2";
        bp2.completionBlock = ^{
            NSLog(@"bp2 complete");
        };
        
        NSArray * keyPathes = @[@"isReady",@"isCancelled",@"isExecuting",@"isFinished"];
        
        [self logOperation:bp1 keyPathes:keyPathes];
        [self logOperation:bp2 keyPathes:keyPathes];
        [self addObserverForOperation:bp1 keyPathes:keyPathes];
        [self addObserverForOperation:bp2 keyPathes:keyPathes];
        
        NSOperationQueue * q = [NSOperationQueue new];
        [bp1 addDependency:bp2];
        [q addOperation:bp1];
        [q addOperation:bp2];
    }
    

    当为bp1添加bp2作为依赖以后,bp1的 isReady 属性从YES置为NO。
    由于bp2是bp1的依赖,所以优先执行bp2。
    在bp2中任务完成之后,-main 方法调用结束之后, -start 方法调用结束之前,bp1调用 -start 并将 isReady 属性置为YES。

    bp1 isReady = YES
    bp1 isCancelled = NO
    bp1 isExecuting = NO
    bp1 isFinished = NO
    
    bp2 isReady = YES
    bp2 isCancelled = NO
    bp2 isExecuting = NO
    bp2 isFinished = NO
    
    bp1 before addDependency:
    observeValueForKeyPath:bp1---isReady---{kind = 1;new = 0;old = 1;}
    bp1 after addDependency:
    
    bp2 before start
    observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 1;old = 0;}
    bp2 before main
    enter bp2
    leave bp2
    bp2 after main
    bp1 before start
    observeValueForKeyPath:bp1---isReady---{kind = 1;new = 1;old = 0;}
    observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 1;old = 0;}
    bp2 complete
    observeValueForKeyPath:bp2---isExecuting---{kind = 1;new = 0;old = 1;}
    bp1 before main
    enter bp1
    observeValueForKeyPath:bp2---isFinished---{kind = 1;new = 1;old = 0;}
    bp2 after start
    leave bp1
    bp1 after main
    bp1 complete
    observeValueForKeyPath:bp1---isExecuting---{kind = 1;new = 0;old = 1;}
    observeValueForKeyPath:bp1---isFinished---{kind = 1;new = 1;old = 0;}
    bp1 after start
    
    NSOperation流程
    • 通过上面的流程图自定义实现简单的并发操作
    @interface SAMOperation : NSOperation
    @end
    @implementation SAMOperation{
        BOOL isFinished;//监听是否执行结束
        BOOL isExecuting;//监听是否正在执行
    }
    
    /*1.自定义初始化方法*/
    -(instancetype)init{
        if (self == [super init]) {
            isExecuting = NO;
            isFinished = NO;
        }
        return self;
    }
    -(void)start{
        //如果被取消了就直接返回结果,不会执行main方法
        if ([self isCancelled]) {
            [self willChangeValueForKey:@"isFinished"];
            isFinished = NO;
            [self didChangeValueForKey:@"isFinished"];
            return;
        }
    
        //没有被取消,使用独立线程执行main方法中的操作
        [self willChangeValueForKey:@"isExecuting"];
        [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
        isExecuting = YES;
        [self didChangeValueForKey:@"isExecuting"];
        
    }
    
    /*2.自定义辅助方法*/
    -(void)main{
        @autoreleasepool{//使用独立的内存释放池,不然会内存泄漏
            @try {
                if (![self isCancelled]) {
                    NSLog(@"Begin%@",[NSThread currentThread]);
                    [NSThread sleepForTimeInterval:1.0];
                    NSLog(@"End%@",[NSThread currentThread]);
                    //任务结束,修改状态值
                    [self willChangeValueForKey:@"isFinished"];
                    [self willChangeValueForKey:@"isExecuting"];
                    isExecuting = NO;
                    isFinished = YES;
                    [self didChangeValueForKey:@"isExecuting"];
                    [self willChangeValueForKey:@"isFinished"];
    
                }
            } @catch (NSException *exception) {
            } @finally {
            }
        }
    }
    
    -(BOOL)isConcurrent{
        return YES;
    }
    
    -(BOOL)isExecuting{
        return isExecuting;
    }
    
    -(BOOL)isFinished{
        return isFinished;
    }
    @end
    

    使用:

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSLog(@"begin func");
        SAMOperation *op1 = [SAMOperation new];
        [op1 start];
        NSLog(@"end func");
    }
    

    NSOperationQueue

    • 两种不同类型的队列:主队列和自定义队列。主队列运行在主线程之上,而自定义队列在后台执行。在两种类型中,这些队列所处理的任务都使用 NSOperation 的子类来表述。
    • NSOperationQueue是一个Operation执行队列,你可以将任何你想要执行的Operation添加到Operation Queue中,以在队列中执行。
    • NSOperationQueue 可以动态的创建多个线程来完成相应Operation,总线程数量通过maxConcurrentOperationCount属性来控制。
    • 当操作添加到队列中,它会待在队列中,直到被显式取消或者执行完为止。
    • 创建队列
    // 主队列队列
    NSOperationQueue *queue = [NSOperationQueue mainQueue];
    // 自定义队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    • 设置最大并发数
      maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行。
      maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
      maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。
    @property NSInteger maxConcurrentOperationCount;
    
    • 取消操作
    - (void)cancelAllOperations;
    

    NSOperationQueue 就相当于管道,Operation以FIFO的形式通过管道,maxConcurrentOperationCount 就是管道数量。下面的demo就是限制总管道数量为1,也就是所有的Operation必须以FIFO形式通过管道,也就是串行。

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

    执行结果:从结果可以看出,maxConcurrentOperationCount = 1 相当于在addOperation的时候就设置了操作之间的依赖。

    operation1-<NSThread: 0x604000270100>{number = 3, name = (null)}
    operation2-<NSThread: 0x604000270540>{number = 4, name = (null)}
    operation2-completionBlock-<NSThread: 0x604000270100>{number = 3, name = (null)}
    operation3-<NSThread: 0x604000270540>{number = 4, name = (null)}
    

    设置queue.maxConcurrentOperationCount = 2;的执行结果

    operation2-<NSThread: 0x60400027be80>{number = 4, name = (null)}
    operation1-<NSThread: 0x600000474140>{number = 3, name = (null)}
    operation3-<NSThread: 0x60400027be80>{number = 4, name = (null)}
    operation2-completionBlock-<NSThread: 0x600000474340>{number = 5, name = (null)}
    
    • 添加Operation的三种方式
    - (void)addOperation:(NSOperation *)op;
    - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    - (void)addOperationWithBlock:(void (^)(void))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
    
    • 简单的自定义Operation
    @interface SAMOperation : NSOperation
    @end
    @implementation SAMOperation
    -(void)main{
        @autoreleasepool{
            @try {
                if (![self isCancelled]) {
                    NSLog(@"Begin");
                    [NSThread sleepForTimeInterval:1.0];
                    NSLog(@"End");
                }
            } @catch (NSException *exception) {
            } @finally {
            }
        }
    }
    @end
    

    使用:

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSLog(@"begin func");
        SAMOperation *op1 = [SAMOperation new];
        NSOperationQueue *queue = [NSOperationQueue new];
        [queue addOperation:op1];
        //[queue waitUntilAllOperationsAreFinished];
        NSLog(@"end func");
    }
    

    打印

    begin func
    end func
    Begin
    End
    
    //把[queue waitUntilAllOperationsAreFinished];注释打开的打印
    begin func
    Begin
    End
    end func
    

    线程安全和 线程同步

    @interface ViewController ()
    @property (nonatomic, assign) NSInteger ticketSurplusCount;
    @property (nonatomic, strong)NSLock *lock;
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.ticketSurplusCount = 50;
        self.lock = [[NSLock alloc] init];
        
        [self initTicketStatusNotSave];
    }
    
    /**
     * 非线程安全:不使用 semaphore
     * 初始化火车票数量、卖票窗口(非线程安全)、并开始卖票
     */
    - (void)initTicketStatusNotSave {
        NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
        NSLog(@"semaphore---begin");
        
        self.ticketSurplusCount = 50;
        
        __weak typeof(self) weakSelf = self;
        
        // queue1 代表北京火车票售卖窗口
        NSOperationQueue *queueO1 = [[NSOperationQueue alloc]init];
        queueO1.maxConcurrentOperationCount = 1;
        
        [queueO1 addOperationWithBlock:^{
            [weakSelf saleTicketNotSafe:@"北京"];
        }];
        // queue2 代表上海火车票售卖窗口
        NSOperationQueue *queueO2 = [[NSOperationQueue alloc]init];
        queueO2.maxConcurrentOperationCount = 1;
        [queueO2 addOperationWithBlock:^{
            [weakSelf saleTicketNotSafe:@"上海"];
        }];
        
        // queue2 代表上海火车票售卖窗口
        NSOperationQueue *queueO3 = [[NSOperationQueue alloc]init];
        queueO3.maxConcurrentOperationCount = 1;
        [queueO3 addOperationWithBlock:^{
            [weakSelf saleTicketNotSafe:@"深圳"];
        }];
    }
    
    
    /**
     * 售卖火车票(非线程安全)
     */
    - (void)saleTicketNotSafe:(NSString*)who{
        while (1) {
            [self.lock lock];
            if (self.ticketSurplusCount > 0) {  //如果还有票,继续售卖
                self.ticketSurplusCount--;
                NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@====%@", (long)self.ticketSurplusCount,who, [NSThread currentThread]]);
                //[NSThread sleepForTimeInterval:0.2];
                [self.lock unlock];
            } else { //如果已卖完,关闭售票窗口
                NSLog(@"所有火车票均已售完");
                 [self.lock unlock];
                break;
            }
        }
    }
    @end
    

    相关文章

      网友评论

        本文标题:NSOperation

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