美文网首页
NSOperation、NSOperationQueue

NSOperation、NSOperationQueue

作者: 眷卿三世 | 来源:发表于2019-08-01 20:17 被阅读0次

    NSOperation、NSOperationQueue学习

    简介:

    为什么要使用 NSOperation、NSOperationQueue?:

    • 可添加完成的代码块,在操作完成后执行。
    • 添加操作之间的依赖关系,方便的控制执行顺序。
    • 设定操作执行的优先级。
    • 可以方便的取消一个操作的执行。
    • 使用KVO观察对操作执行状态的更改:isExecuteing、isFinished、isCancelled。

    概念:

    操作(Operation):

    • 执行操作的意思,在GCD中就是block中的代码,在NSOperation中,我们使用NSOperation子类、NSInvocationOperation、NSBlockOperation,或者自定义子类来封装操作。

    操作队列:

    • 即用来存放操作的队列。不同于GCD中的调用队列FIFO的原则。NSOperationQueue对于添加到队列中的操作,首先进入就绪状态(就绪状态取决于操作之间的依赖关系),然后进入就绪状态的操作开始执行(非结束顺序执行),根据操作自身属性设置的优先级决定
    • 操作队列通过设置最大并发操作数,来控制并发和串行。
    • NSOperationQueue为开发者提供两种不同类型的队列,主队列和自定义队列,主队列在主线程之上,自定义队列在后台执行。

    NSOperation:

    NSInvocationOperation

    /**
     * 使用子类 NSInvocationOperation
     */
    - (void)useInvocationOperation {
    
        // 1.创建 NSInvocationOperation 对象
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    
        // 2.调用 start 方法开始执行操作
        [op start];
    }
    
    /**
     * 任务1
     */
    - (void)task1 {
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
            NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
        }
    }
    

    结果:
    在不使用NSOperationQueue的情况下,单独使用NSInvocationOperation不会创建新线程,使用的是当前线程处理所有操作。
    在其他线程单独调用NSInvocationOperation,不会开启新线程,使用其他线程来完成任务操作。

    NSBlockOperation

    当单独使用blockOperationWithBlock时,任务只会使用当前线程,不会开启新线程处理任务。
    如果想开启新线程执行并行任务,需要结合是使用addExecutionBlock方法来实现。代码如下:

    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];
                 NSLog(@"1---%@", [NSThread currentThread]);
            }
        }];
        
        [op addExecutionBlock:^{
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];
                 NSLog(@"2---%@", [NSThread currentThread]);
            }
        }];
        
        [op addExecutionBlock:^{
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];
                 NSLog(@"3---%@", [NSThread currentThread]);
            }
        }];
        
        [op addExecutionBlock:^{
            for (int i = 0; i < 2; ++i) {
                [NSThread sleepForTimeInterval:2];
                 NSLog(@"4---%@", [NSThread currentThread]);
            }
        }];
        
        [op start];
    

    NSOperationQueue

    1、创建NSBlockOperation、NSInvocationOperation任务操作,并加入到队列中之后,可以开启新的线程,并发执行。

    2、使用addOperationWithBlock加入队列中的操作也能够开启新的线程进行并发执行。

    控制并发和串行:

    注意:maxConcurrentOperationCount 控制的不是并发线程的数量,而是一个队列中同时能并发执行的最大操作数。而且一个操作也并非只能在一个线程中运行。

    • maxConcurrentOperationCount 默认情况下为-1,表示不进行限制,可进行并发执行。
    • maxConcurrentOperationCount 为1时,队列为串行队列。只能串行执行
    • maxConcurrentOperationCount 大于1时,队列为并发队列。操作并发执行,当然这个值不应超过系统限制,即使自己设置一个很大的值,系统也会自动调整为 min{自己设定的值,系统设定的默认最大值}。

    优先级

    NSOperation提供了queuePriority(优先级)属性,queuePriority属性适用于同一操作队列中的操作,不适用于不同操作队列的操作。默认情况下,所有新创建的操作对象优先级都是NSOperationQueuePriorityNormal,但是我们可以通过setQueuePriority:方法来改变当前操作在同一队列中的执行优先级。

    // 优先级的取值
    typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
        NSOperationQueuePriorityVeryLow = -8L,
        NSOperationQueuePriorityLow = -4L,
        NSOperationQueuePriorityNormal = 0,
        NSOperationQueuePriorityHigh = 4,
        NSOperationQueuePriorityVeryHigh = 8
    };
    

    在优先级中,需要注意的点:
    1、优先级不能取代依赖关系,也就是在有依赖关系的时候,以依赖为先,其次是优先级。
    2、在队列中就绪的操作,优先级高的先执行。

    线程间通信

    在 iOS 开发过程中,我们一般在主线程里边进行 UI 刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

    // 1.创建队列
        NSOperationQueue *queue = [[NSOperationQueue alloc] init];
        // 2.添加操作
        [queue addOperationWithBlock:^{
            // 异步进行耗时操作
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                NSLog(@"1---%@", [NSThread currentThread]); // 打印当前线程
            }
            
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                // 进行一些 UI 刷新等操作
                for (int i = 0; i < 2; i++) {
                    [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
                    NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
                }
            }];
        }];
    

    安全:

    当多个线程同时有写操作,同时操作同一个全局变量时,有安全问题。
    解决:同步加锁

    - (void)initTicketStatusSave {
        NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程
        
        self.ticketSurplusCount = 50;
        self.lock = [[NSLock alloc] init];
        
        NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
        
        NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
        
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            [self saleTicketSafe];
        }];
        
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            [self saleTicketSafe];
        }];
        
        // 5.添加操作,开始卖票
        [queue1 addOperation:op1];
        [queue2 addOperation:op2];
    }
    
    - (void)saleTicketSafe {
        while (1) {
            // 加锁
            [self.lock lock];
            
            if (self.ticketSurplusCount > 0) {
                // 如果还有票,继续售卖
                self.ticketSurplusCount--;
                NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%ld 窗口:%@", (long)self.ticketSurplusCount, [NSThread currentThread]]);
                [NSThread sleepForTimeInterval:.2];
            }
            
            [self.lock unlock];
            
            if (self.ticketSurplusCount <= 0) {
                NSLog(@"所有火车票均已售完");
                break;
            }
        }
    }
    

    iOS 实现线程加锁有很多种方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock等等各种方式。

    常用属性和方法归纳

    NSOperation 常用属性和方法

    1、取消操作方法

    • (void)cancel; 可取消操作,实质是标记 isCancelled 状态。
      2、判断操作状态方法

    • (BOOL)isFinished; 判断操作是否已经结束。

    • (BOOL)isCancelled; 判断操作是否已经标记为取消。

    • (BOOL)isExecuting; 判断操作是否正在在运行。

    • (BOOL)isReady; 判断操作是否处于准备就绪状态,这个值和操作的依赖关系相关。
      3、操作同步

    • (void)waitUntilFinished; 阻塞当前线程,直到该操作结束。可用于线程执行顺序的同步。

    • (void)setCompletionBlock:(void (^)(void))block; completionBlock 会在当前操作执行完毕时执行 completionBlock。

    • (void)addDependency:(NSOperation *)op; 添加依赖,使当前操作依赖于操作 op 的完成。

    • (void)removeDependency:(NSOperation *)op; 移除依赖,取消当前操作对操作 op 的依赖。
      @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在当前操作开始执行之前完成执行的所有操作对象数组。

    NSOperationQueue 常用属性和方法

    1、取消/暂停/恢复操作

    • (void)cancelAllOperations; 可以取消队列的所有操作。
    • (BOOL)isSuspended; 判断队列是否处于暂停状态。 YES 为暂停状态,NO 为恢复状态。
    • (void)setSuspended:(BOOL)b; 可设置操作的暂停和恢复,YES 代表暂停队列,NO 代表恢复队列。
      2、操作同步
    • (void)waitUntilAllOperationsAreFinished; 阻塞当前线程,直到队列中的操作全部执行完毕。
      3、添加/获取操作
    • (void)addOperationWithBlock:(void (^)(void))block; 向队列中添加一个 NSBlockOperation 类型操作对象。
    • (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向队列中添加操作数组,wait 标志是否阻塞当前线程直到所有操作结束
    • (NSArray *)operations; 当前在队列中的操作数组(某个操作执行结束后会自动从这个数组清除)。
    • (NSUInteger)operationCount; 当前队列中的操作数。
      4、获取队列
    • (id)currentQueue; 获取当前队列,如果当前线程不是在 NSOperationQueue 上运行则返回 nil。
    • (id)mainQueue; 获取主队列。

    注意:
    1、这里的暂停和取消(包括操作的取消和队列的取消)并不代表可以将当前的操作立即取消,而是当当前的操作执行完毕之后不再执行新的操作。
    2、暂停和取消的区别就在于:暂停操作之后还可以恢复操作,继续向下执行;而取消操作之后,所有的操作就清空了,无法再接着执行剩下的操作。

    相关文章

      网友评论

          本文标题:NSOperation、NSOperationQueue

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