美文网首页调试ios多线程
NSOperation和NSOperationQueue相关

NSOperation和NSOperationQueue相关

作者: 纳木错_grace | 来源:发表于2018-03-20 20:44 被阅读1632次

    苹果在并发编程方面,除了提供有GCD外,还有NSOperation【操作】NSOperationQueue【操作队列】组合。GCD是基于C的底层API,而NSOperation则是封装GCD实现Objective-C API。NSOperationQueue系统给出了许多api,能够很方便的实现GCD需要大量代码的事情【比如:操作队列可以取消在任务处理队列中的任务】。因此,NSOperationQueue被推荐使用。虽然被推荐使用,但个人认为,还是根据自己的编程习惯。如果用着GCD顺手而且也能完全满足自己开发中的需求。就不必非得换用别的。
    本文将通过介绍NSOperation和NSOperationQueue的相关用法演示,做以自己日常学习的总结,也希望能够给看到这篇文章的读者们一个入门介绍。

    文章内容目录:
    • 1,NSOperation和NSOperationQueue的介绍。
    • 2,NSInvocationOperation的使用
    • 3,NSBlockOperation的使用
    • 4,自定义Operation的步骤
    • 5,其它
    • 6,总结
    1、 NSOperation和NSOperationQueue的介绍。
    1.1、NSOperation

    NSOperation是系统提供的抽象的基类,我们使用的时候需要使用继承于它的子类。系统为我们提供了两种继承于NSOperation的子类,分别是NSInvocationOperationNSBlockOperation,大多情况下,我们用这两个系统提供的子类就能满足我们并发编程的任务,但是如果你不想用系统提供的这两个类,那么你可以根据自己的需求来自定义自己的操作类。文章后边会结束自定义操作类的时候的步骤和注意事项。

    1.2、NSOperationQueue
    • NSOperationQueue【操作队列】:用来存放操作的队列。是由GCD提供的一个队列模型的Cocoa抽象。GCD提供了更加底层的控制,而操作队列则在GCD之上实现了一些方便的功能。
    • NSOperationQueue操作队列中的任务的执行顺序收到任务的isReady【就绪状态】状态和任务的队列优先级影响。这和GCD中的队列FIFO的执行顺序有很大区别。
    • 我们可以通过设置NSOperationQueue最大并发操作数(maxConcurrentOperationCount)来控制任务执行是并发还是串行。
    • NSOperationQueue有两种不通类型的队列:主队列和自定义队列。主队列在主线程上运行,而自定义队列在后台执行。这两种队列中加入的任务都需要用NSOperation的子类来表示。
      注:NSOperation【操作】通俗的讲,就是我们写在程序里的一段代码。
    2、 NSInvocationOperation的使用
    2.1、手动执行操作。
    /**
     在主线程中执行
     */
    - (void)executeInMainThread
    {
        NSLog(@"创建操作:%@",[NSThread currentThread]);
    //1:创建操作。
        NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task) object:nil];
    //2:开始执行操作。
        [op start];
    }
    
    /**
     在子线程中执行
     */
    - (void)executeInNewThread
    {
        NSLog(@"创建操作:%@",[NSThread currentThread]);
        [NSThread detachNewThreadSelector:@selector(executeInMainThread) toTarget:self withObject:nil];
    }
    
    - (void)task
    {
        NSLog(@"执行操作:%@",[NSThread currentThread]);
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"----%@",[NSThread currentThread]);
        }
    }
    
    
    主线程.png 子线程.png

    由上图运行结果(在主线程和子线程中分别创建操作,并且手动触发)可以看出:手动创建操作并调用start方法触发操作的情况下,操作任务会在创建操作的线程中执行。

    2.2、和NSOperationQueue配合使用。
    • 2.2.1、将操作添加到主操作队列。
    - (void)addMainOperationQueue
    {
    //获得主操作队列
       NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
       self.mainQueue = mainQueue;
       NSLog(@"创建添加任务%@",[NSThread currentThread]);
       NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
       [mainQueue addOperation:op1];
       [mainQueue cancelAllOperations];
       NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
       [mainQueue addOperation:op2];
       
       NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
       [mainQueue addOperation:op3];
       
       [op3 cancel];//取消某个操作,可以直接调用操作的取消方法cancel。
       //取消整个操作队列的所有操作,这个方法好像没有效果???。在主队列中,没有用,如果将操作加入到自定义队列的话,在操作没有开始执行的时候,是能够取消操作的。
    //    [mainQueue cancelAllOperations];
       
       NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
       [mainQueue addOperation:op4];
    }
    - (void)task:(NSString *)order
    {
       for (NSInteger i = 0; i < 3; i++) {
           NSLog(@"任务:%@----%@",order,[NSThread currentThread]);
       }
    }
    
    主队列串行执行.png

    由上图运行结果可以看出,当将操作添加到主操作队列时,所有操作会按照添加到队列中的先后顺序串行依次执行。任务在主线程中执行。

    • 2.2.2、将操作添加到自定义操作队列。
    - (void)addCustomeOperationQueue
    {
        NSLog(@"创建添加任务%@",[NSThread currentThread]);
        NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
         customQueue.maxConcurrentOperationCount = 5;//这个属性的设置需在队列中添加任务之前。任务添加到队列后,如果该任务没有依赖关系的话,任务添加到队列后,会直接开始执行。
        //加入到自定义队列里的任务,可以通过设置操作队列的 maxConcurrentOperationCount的值来控制操作的串行执行还是并发执行。
        
        //当maxConcurrentOperationCount = 1的时候,是串行执行。 maxConcurrentOperationCount > 1的时候是并发执行,但是这个线程开启的数量最终还是由系统决定的,不是maxConcurrentOperationCount设置为多少,就开多少条线程。默认情况下,自定义队列的maxConcurrentOperationCount值为-1,表示并发执行。
        NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
         [customQueue addOperation:op1];
      
        NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
    
        NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
        
        NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
        
        //打断点在op1加入队列前后的状态值。
        ///<NSInvocationOperation 0x608000246cf0 isFinished=NO isReady=YES isCancelled=NO isExecuting=NO>
       
        ///<NSInvocationOperation 0x608000246cf0 isFinished=NO isReady=YES isCancelled=NO isExecuting=YES>
      
        [customQueue addOperation:op2];
    //    [customQueue cancelAllOperations];//这个方法只能取消还未开始执行的操作,如果操作已经开始执行,那么该方法依然取消不了。
        [customQueue addOperation:op3];
        [customQueue addOperation:op4];
    }
    
    自定义队列并发.png

    当将任务添加到自定义队列的时候,会开启子线程,操作会并发执行。

    2.3、添加依赖。
    • 2.3.1、主队列
    //依赖关系的设置需要在任务添加到队列之前。
    - (void)addDependenceInMain//这个添加依赖的用途有点类似GCD中的队列组
    {
        NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
        NSLog(@"创建添加任务%@",[NSThread currentThread]);
        NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
        
        
        NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
     
        
        NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
        
        
        NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
        
        [op1 addDependency:op2];//由此更可以看出,如果添加了依赖关系,在主队列串行执行任务,也不是先进先出的规则。而是按照依赖关系的属性执行。  应该把操作的所有配置都配置好后,再加入队列,因为加入队列后,操作就开始执行了,再进行配置就晚了。
        [mainQueue addOperation:op1];
        [mainQueue addOperation:op2];
        [mainQueue addOperation:op3];
        [mainQueue addOperation:op4];
    }
    
    主队列依赖.png
    由上图的运行结果可以看出,虽然操作op1先于op2加入到队列中,但是给op1添加了依赖关系。必须op2执行完毕后,才会执行op1,这里的操作队列就和GCD中的队列不一样了,GCD队列是遵守FIFO规则,而这里的队列里的操作则是根据依赖关系等决定。这种依赖关系是单向的,op1依赖于op2,op2的执行与op1没有任何关系。不能设置双向依赖,如果op1依赖op2,op2又反过来依赖op1,则会出现互相等待的死锁情况。

    注意:关于操作队列与操作的相关配置都要在操作加入到队列前配置完全,因为操作加入到队列后,就开始执行操作了。此时,再进行操作或队列的设置很有可能达不到预期的效果。

    • 2.3.2、自定义队列
    - (void)addDependenceInCustom
    {
        NSLog(@"创建添加任务%@",[NSThread currentThread]);
        NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
        customQueue.maxConcurrentOperationCount = 5;
        NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"1"];
        
        NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"2"];
        
        NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"3"];
        
        NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"4"];
        
        //isReady属性如果为YES的时候,该任务处于就绪状态,就等待系统调度执行。如果任务有依赖关系的话,该任务的isReady属性需要在其所属的依赖任务执行完成后,才为YES。
        //默认情况下,操作的queuePriority(队列优先级)为0 NSOperationQueuePriorityNormal 正常优先级。
        //都处于就绪状态下的操作,才开始按照优先级顺序来执行。 优先级高的是说系统调度的概率会大一些,但是也不能确保完全会按照优先级来,如果要设置操作的执行顺序,最有效也最安全的做法还是设置依赖关系。
        [op1 addDependency:op2];
        [op2 addDependency:op3];
        /*注意:两个任务不能相互依赖,如果相互依赖,则会出现死锁,都执行不了
         [op1 addDependency:op2];
         [op2 addDependency:op1];
         这种相互依赖是错误的。
         */
        op1.queuePriority = NSOperationQueuePriorityVeryHigh;
        op2.queuePriority = NSOperationQueuePriorityHigh;
        op3.queuePriority = NSOperationQueuePriorityLow;
        [customQueue addOperation:op1];
        [customQueue addOperation:op2];
        [customQueue addOperation:op3];
        [customQueue addOperation:op4];
    }
    
    自定义队列依赖.png

    由上图运行结果可以看出,虽然在自定义队列中操作是并发执行的,但如果添加了依赖关系的话,op1依赖于op2,op1要在op2执行完全后,才会执行。

    依赖 VS 队列优先级(queuePriority)
    操作有个isReady属性,该属性表示操作时否处于就绪状态,处于就绪状态的操作,只要等待系统调度,就会执行。而操作的就绪状态取决于依赖关系,当op1依赖于op2的时候,如果op2还没执行完,op1的isReady = NO,即op1还处于未就绪状态。同处于就绪状态的操作,此时再比较它们的队列优先级(queuePriority),这样才有意义。
    队列中会先执行处于就绪状态的操作,即便处于就绪状态的操作的队列优先级低于未就绪的操作。所以,要控制操作之间的执行顺序,需要使用依赖关系。

    3、 NSBlockOperation的使用
    3.1、手动执行操作。
    - (void)executeInMainThread
    {
        NSLog(@"创建操作:%@",[NSThread currentThread]);
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            [self task];
        }];
        //NSBlockOperation该操作有个方法能在该操作中持续添加操作任务addExecutionBlock,直到全部的block中的任务都执行完成后,该操作op才算执行完毕。当该操作在addExecutionBlock加入比较多的任务时,该op的block中的(包括blockOperationWithBlock和addExecutionBlock中的操作)会在新开的线程中执行。不一定在创建该op的线程中执行。
        [op addExecutionBlock:^{
            [self task:@"add"];
        }];
        [op addExecutionBlock:^{
            [self task:@"add"];
        }];
        [op addExecutionBlock:^{
            [self task:@"add"];
        }];
        [op addExecutionBlock:^{
            [self task:@"add"];
        }];
        [op addExecutionBlock:^{
            [self task:@"add"];
        }];
        [op addExecutionBlock:^{
            [self task:@"add"];
        }];
        [op addExecutionBlock:^{
            [self task:@"add"];
        }];
        [op addExecutionBlock:^{
            [self task:@"add"];
        }];
        [op start];
    }
    - (void)executeInNewThread
    {
        [NSThread detachNewThreadSelector:@selector(executeInMainThread) toTarget:self withObject:nil];
    }
    - (void)task
    {
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"操作执行:%@",[NSThread currentThread]);
        }
    }
    
    block手动.png

    由上图运行结果,可以看出,当一个操作中多次添加任务的话,系统会开启新的线程,并发执行操作中的任务。

    相比NSInvocationOperation,NSBlockOperation多了一个后续持续给该操作添加任务的方法,[op addExecutionBlock:^{ }]【注意:针对一个操作,blockOperationWithBlock里的任务和addExecutionBlock里的任务都执行完了,才算该操作执行结束】

    3.2、和NSOperationQueue联合使用。
    • 3.2.1:添加到主操作队列
    - (void)addMainOperationQueue
    {
        NSLog(@"任务创建:%@",[NSThread currentThread]);
        NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            [self task:@"1"];
        }];
    
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            [self task:@"2"];
        }];
    
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            [self task:@"3"];
        }];
     
        [mainQueue addOperation:op1];
        [mainQueue cancelAllOperations];
    //    [op2 addDependency:op1];
        [mainQueue addOperation:op2];
        [mainQueue addOperation:op3];
        
        
        [op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];
        [op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];
        [op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];
        [op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];
        [op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];
        [op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];
        [op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];[op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];
        [op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];
        [op2 addExecutionBlock:^{
            [self task:@"2.1"];
        }];
        [op3 addExecutionBlock:^{
            [self task:@"3.1"];
        }];
        //将操作加入到主队列中后,根据操作添加到队列中的先后顺序(操作之间没有添加依赖关系),串行执行。每个操作addExecutionBlock添加的任务和blockOperationWithBlock中的任务共同组成一个操作。两个block中的操作都执行结束后,一个操作才算结束。
        //虽然将操作加到了NSOperationQueue主操作队列,但是当操作中addExecutionBlock加的任务比较多的时候,操作block中的任务会在新的线程中并发执行,但是对于操作来说,操作时串行执行的。
    }
    
    主队列串行执行操作.png
    • 3.2.2:添加到自定义操作队列
    - (void)addCustomOperationQueue
    {
        NSLog(@"任务创建:%@",[NSThread currentThread]);
        NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            [self task:@"1"];
        }];
        
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            [self task:@"2"];
        }];
        
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            [self task:@"3"];
        }];
        
        
        [customQueue addOperation:op1];
        [customQueue addOperation:op2];
        [customQueue addOperation:op3];
        [op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];
        
    }
    
    自定义队列操作并发.png

    由运行结果可以看出,当将操作添加到自定义队列的话,如果不设置最大并发数maxConcurrentOperationCount的话,操作是并发执行的。当将maxConcurrentOperationCount设置为1的时候,操作串行执行。

    3.3、添加依赖。
    - (void)addDependenceInCustom
    {
        NSLog(@"任务创建:%@",[NSThread currentThread]);
        NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
        NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
            [self task:@"1"];
        }];
        
        NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
            [self task:@"2"];
        }];
        
        NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            [self task:@"3"];
        }];
        
        [op1 addDependency:op2];
        [op3 addDependency:op1];
        [customQueue addOperation:op1];
        [customQueue addOperation:op2];
        [customQueue addOperation:op3];
        [op1 addExecutionBlock:^{
            [self task:@"1.1"];
        }];
    }
    
    自定义block依赖.png

    NSBlockOperation添加依赖的做法和NSInvocationOperation的写法一样,这里需要注意一下的是:由上图也可以看出结果:op3的执行是在op1完全执行后才执行的。而op1中blockOperationWithBlock和addExecutionBlock这两种block里的任务都执行完,才算op1操作执行结束

    4、 自定义Operation的步骤

    一般来说,系统提供的两个NSOperation的两个子类NSInvocationOperationNSBlockOperation就能满足开发需求了。但是,如果不想用系统提供的类,可以自定义自己的操作类。继承于NSOperation类。最起码要实现两个方法。
    1,初始化对象的方法。
    2,重写父类的main方法,这个方法里是你的任务代码。

    • 定义
    #import "CustomOperation.h"
    
    @interface CustomOperation()
    @property (nonatomic,strong)id data;//作为该操作的参数。
    @end
    
    @implementation CustomOperation
    - (instancetype)initWithData:(id)data
    {
        if (self = [super init]) {
            self.data = data;
        }
        return self;
    }
    - (void)main//只重写了这个方法的话,如果单独手动执行该自定义操作的话,操作时同步执行的,如果操作队列联合起来使用的话,也会并发执行操作。
    {
        NSString *order = (NSString *)self.data;
        for (NSInteger i = 0; i < 3; i++) {
            NSLog(@"自定义操作%@----:%@",order,[NSThread currentThread]);
        }
    }
    @end
    
    • 使用
    - (void)executeInMain
    {
        CustomOperation *op = [[CustomOperation alloc]initWithData:@""];
        [op start];
    }
    
    //和操作队列联合使用
    - (void)addMainQueue
    {
        NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
        CustomOperation *op1 = [[CustomOperation alloc]initWithData:@"1"];
        CustomOperation *op2 = [[CustomOperation alloc]initWithData:@"2"];
        CustomOperation *op3 = [[CustomOperation alloc]initWithData:@"3"];
        
        [mainQueue addOperation:op1];
        [mainQueue addOperation:op2];
        [mainQueue addOperation:op3];
    }
    
    - (void)addCustomQueue
    {
        NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
        CustomOperation *op1 = [[CustomOperation alloc]initWithData:@"1"];
        CustomOperation *op2 = [[CustomOperation alloc]initWithData:@"2"];
        CustomOperation *op3 = [[CustomOperation alloc]initWithData:@"3"];
        
        [customQueue addOperation:op1];
        [op1 cancel];
        [customQueue addOperation:op2];
        [customQueue addOperation:op3];
    }
    
    5、 其它
    • NSOperation的一些常用属性和方法。
    //操作是否取消
    @property (readonly, getter=isCancelled) BOOL cancelled;
    - (void)cancel;//取消操作
    @property (readonly, getter=isExecuting) BOOL executing;//是否执行
    @property (readonly, getter=isFinished) BOOL finished;//是否执行结束
    @property (readonly, getter=isAsynchronous) BOOL asynchronous //是否异步
    @property (readonly, getter=isReady) BOOL ready;//是否处于就绪状态
    - (void)addDependency:(NSOperation *)op;//添加依赖
    - (void)removeDependency:(NSOperation *)op;//去除依赖
    @property (readonly, copy) NSArray<NSOperation *> *dependencies;//所有相关依赖
    - (void)waitUntilFinished;//执行该操作的时候阻塞当前线程,直到该操作执行
    结束。
    - (void)addOperationsInCustom
    {
        NSInvocationOperation *op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait1"];
           NSInvocationOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait2"];
           NSInvocationOperation *op3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait3"];
           NSInvocationOperation *op4 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task:) object:@"wait4"];
        NSOperationQueue *customQueue = [[NSOperationQueue alloc]init];
    //    [customQueue addOperation:op1];
    //    [op1 waitUntilFinished];//waitUntilFinished是阻塞当前线程的作用,在这里会阻塞主线程,阻塞主线程中继续往队列中加任务,直到该op1操作执行结束,这样就能实现操作的串行了。
          [customQueue addOperation:op2];
         [op2 waitUntilFinished];
        [customQueue addOperation:op3];
        [op3 waitUntilFinished];
        [customQueue addOperation:op4];
    }
    
    • NSOperationQueue的相关属性和方法
    - (void)addOperation:(NSOperation *)op;
    - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait ;//可以根据wait参数设置队列中操作的执行方式是串行还是并发。
    - (void)addOperationWithBlock:(void (^)(void))block;
    @property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
    @property (readonly) NSUInteger operationCount ;//操作的个数
    @property NSInteger maxConcurrentOperationCount;//最大并发数
    @property (getter=isSuspended) BOOL suspended;//是否悬挂
    - (void)cancelAllOperations;//取消队列中的所有操作。
    @property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue;//操作队列对应的GCD队列。
    
    队列的GCD.png
    6、 总结

    至此;非常感谢您看到这里。😁
    自己总结了一下NSOperation相关的知识点,也又重新理顺了自己的一些想法。但是,这些知识点不用的话,还是会很快忘记的,最好的理解和掌握是在运用在实践中。在以后的实践运用中如果有新内容或新的想法,会持续更新。
    文章中的源码已经上传。Demo传送
    如有疑问或错误,欢迎指正和提问。谢谢!!!
    参考
    非常感谢以下文章的作者。谢谢。
    iOS多线程:『NSOperation、NSOperationQueue』详尽总结

    iOS 并发编程之 Operation Queues

    相关文章

      网友评论

        本文标题:NSOperation和NSOperationQueue相关

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