iOS基础深入补完计划--NSOperation

(由于合在一起感觉一篇太长翻着累)

iOS多线程目前总结了四篇

欢迎移步O(∩_∩)O

目录

  • NSOperation
  • 队列与操作
    • 队列NSOperationQueue
    • 操作NSOperation
  • 阻塞
  • NSOperation的API

NSOperation

NSOperation是苹果GCD、面向对象的封装。

相比GCD的优点:
队列与操作:

既然是GCD的封装、自然逃不掉GCD的基本概念。操作与队列。

  • 队列NSOperationQueue

三种向队列添加操作的方式

- (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));
队列插入操作后的执行顺序:

首先、无论串行并行(并发数是否为1)的条件下。队列内操作的执行依赖两个要素。

  • 如果所插入的操作存在依赖关系、优先完成依赖操作。
  • 如果所插入的操作不存在依赖关系、队列并发数为1下采用先进先出的原则、反之直接开辟新的线程执行
    具体可以看下面的例子:
    //创建操作队列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    //创建最后一个操作
    NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"最后的任务");
    }];
    for (int i=0; i<5-1; ++i) {
        //创建多线程操作
        NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
            sleep(i);
            NSLog(@"第%d个任务",i);
        }];
        //设置依赖操作为最后一个操作
        [blockOperation addDependency:lastBlockOperation];
        [operationQueue addOperation:blockOperation];
        
    }
    //将最后一个操作加入线程队列
    [operationQueue addOperation:lastBlockOperation];

有依赖的情况下输出:

2018-03-16 15:08:21.666983+0800 test[4524:404134] 最后的任务
2018-03-16 15:08:21.667267+0800 test[4524:404135] 第0个任务
2018-03-16 15:08:22.667647+0800 test[4524:404141] 第1个任务
2018-03-16 15:08:23.672276+0800 test[4524:404134] 第2个任务
2018-03-16 15:08:24.669316+0800 test[4524:404132] 第3个任务

相对的、我们可以取消依赖:注释掉[blockOperation addDependency:lastBlockOperation];

2018-03-16 15:09:18.637169+0800 test[4551:406003] 第0个任务
2018-03-16 15:09:19.641270+0800 test[4551:406002] 第1个任务
2018-03-16 15:09:19.641270+0800 test[4551:406013] 最后的任务
2018-03-16 15:09:20.640994+0800 test[4551:406006] 第2个任务
2018-03-16 15:09:21.637335+0800 test[4551:406005] 第3个任务

我们也可以看看多线程的提现、另1号操作依赖addDependency:lastBlockOperation。看看其他操作会不会直接执行。

//设置依赖操作为最后一个操作
NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
       sleep(i);
       NSLog(@"第%d个任务",i);
}];
//设置依赖操作为最后一个操作
if (i == 1) {
     [blockOperation addDependency:lastBlockOperation];
}
[operationQueue addOperation:blockOperation];

结果确实是除了1号操作、其他的都立即被分配的其他线程执行了。

2018-03-16 15:20:16.679755+0800 test[4753:424790] 第0个任务
2018-03-16 15:20:18.682874+0800 test[4753:424792] 第2个任务
2018-03-16 15:20:18.682875+0800 test[4753:424795] 最后的任务
2018-03-16 15:20:19.683923+0800 test[4753:424793] 第3个任务
2018-03-16 15:20:19.683923+0800 test[4753:424790] 第1个任务
需要注意的是:
  • 依赖必须在操作被添加到队列(确切来说应该是被执行)之前设置、否则无效。比如我们将一下两句调转:
[operationQueue addOperation:blockOperation];
[blockOperation addDependency:lastBlockOperation];

输出的结果和注释掉依赖后相同:

2018-03-16 15:10:48.814888+0800 test[4590:408731] 第0个任务
2018-03-16 15:10:49.817847+0800 test[4590:408732] 第1个任务
2018-03-16 15:10:49.817850+0800 test[4590:408731] 最后的任务
2018-03-16 15:10:50.815294+0800 test[4590:408733] 第2个任务
2018-03-16 15:10:51.815585+0800 test[4590:408730] 第3个任务
  • 依赖在添加进队列之后虽然不能追加。但是可以对某操作进行追加addExecutionBlock、也可以延后操作的执行。
- (void)operationTest {
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    
    NSBlockOperation * blockOperation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"进入操作1");
        sleep(3);
        NSLog(@"操作1完成");
    }];
    
    NSBlockOperation * blockOperation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"进入依赖操作");
    }];
    
    [blockOperation2 addDependency:blockOperation1];
    
    [operationQueue addOperation:blockOperation1];
    [operationQueue addOperation:blockOperation2];
    
    [blockOperation1 addExecutionBlock:^{
        NSLog(@"进入追加操作");
        sleep(5);
        NSLog(@"追加操作完成");
    }];
}

打印结果:

2018-03-20 13:00:55.635675+0800 test[2123:222591] 进入操作1
2018-03-20 13:00:55.635675+0800 test[2123:225319] 进入追加操作
2018-03-20 13:00:58.639154+0800 test[2123:222591] 操作1完成
2018-03-20 13:01:00.641240+0800 test[2123:225319] 追加操作完成
2018-03-20 13:01:00.641511+0800 test[2123:225319] 进入依赖操作

  • 操作的依赖关系与本身绑定、并不受限于同一个队列。即使所执行的队列不同、也可以完成依赖操作。
    NSOperationQueue *operationQueue1=[[NSOperationQueue alloc]init];
    NSOperationQueue *operationQueue2=[[NSOperationQueue alloc]init];
    //创建最后一个操作
    NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
        sleep(3);
        NSLog(@"最后的任务");
    }];
    NSBlockOperation *blockOperation0=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"第0个任务");
    }];
    NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"第1个任务");
    }];
    [blockOperation1 addDependency:lastBlockOperation];
    [operationQueue1 addOperation:blockOperation1];
    [operationQueue1 addOperation:blockOperation0];
    
    [operationQueue2 addOperation:lastBlockOperation];

打印结果:

2018-03-16 15:28:26.517760+0800 test[4874:439297] 第0个任务
2018-03-16 15:28:29.520710+0800 test[4874:439298] 最后的任务
2018-03-16 15:28:29.521107+0800 test[4874:439299] 第1个任务
操作的追加

我们可以通过一下方法将新的操作追加到NSBlockOperation对象中

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

但需要注意的是、追加的操作是并发执行的。具体的最大并发数、应该是由系统决定(因为我没找到哪个属性可以设置)。

    //主队列、必然是并发为1
    NSOperationQueue *operationQueue=[NSOperationQueue mainQueue];
    
    NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"第%d个任务--%@",10,[NSThread currentThread]);
    }];
    
    
    for (int i=0; i<10; ++i) {
        [blockOperation addExecutionBlock:^{
            sleep(1);
            NSLog(@"第%d个任务--%@",i,[NSThread currentThread]);
        }];
    }
    
    [operationQueue addOperation:blockOperation];

打印结果

2018-03-16 16:21:38.739533+0800 test[5491:504081] 第0个任务--<NSThread: 0x60400026ea80>{number = 3, name = (null)}
2018-03-16 16:21:38.739535+0800 test[5491:504020] 第10个任务--<NSThread: 0x60400006a000>{number = 1, name = main}
2018-03-16 16:21:38.739537+0800 test[5491:504083] 第1个任务--<NSThread: 0x60400026a3c0>{number = 4, name = (null)}
2018-03-16 16:21:38.739565+0800 test[5491:504080] 第2个任务--<NSThread: 0x60000026ba80>{number = 5, name = (null)}
2018-03-16 16:21:39.739936+0800 test[5491:504083] 第5个任务--<NSThread: 0x60400026a3c0>{number = 4, name = (null)}
2018-03-16 16:21:39.739936+0800 test[5491:504020] 第4个任务--<NSThread: 0x60400006a000>{number = 1, name = main}
2018-03-16 16:21:39.739936+0800 test[5491:504080] 第6个任务--<NSThread: 0x60000026ba80>{number = 5, name = (null)}
2018-03-16 16:21:39.739936+0800 test[5491:504081] 第3个任务--<NSThread: 0x60400026ea80>{number = 3, name = (null)}
2018-03-16 16:21:40.741238+0800 test[5491:504020] 第7个任务--<NSThread: 0x60400006a000>{number = 1, name = main}
2018-03-16 16:21:40.741239+0800 test[5491:504083] 第8个任务--<NSThread: 0x60400026a3c0>{number = 4, name = (null)}
2018-03-16 16:21:40.741290+0800 test[5491:504080] 第9个任务--<NSThread: 0x60000026ba80>{number = 5, name = (null)}
  • 操作NSOperation

两种个子类NSBlockOperationNSInvocationOperation、(当然、你也可以继承出一个NSOperation)。

操作的优先级

优先级只体现在两个时间点:

  • 依赖任务处理完成、队列对后续任务的调度。
  • 依赖队列从暂停转变为重新启动、后续任务的调度。

简而言之就是。在队列同时需要调度执行的任务中、会按照优先级排序执行。

如下所示(举个依赖完成的例子):

    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    
    operationQueue.maxConcurrentOperationCount = 1;
    
    NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"低优先级任务");
    }];
    blockOperation1.queuePriority = NSOperationQueuePriorityLow;
    
    NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"高优先级任务");
        sleep(1);
    }];
    blockOperation2.queuePriority = NSOperationQueuePriorityHigh;
    
    NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
        
    }];
    
    [blockOperation1 addDependency:blockOperation3];
    [blockOperation2 addDependency:blockOperation3];
    
    [operationQueue addOperation:blockOperation1];
    [operationQueue addOperation:blockOperation2];
    [operationQueue addOperation:blockOperation3];

打印结果:

2018-03-16 18:33:52.435983+0800 test[6880:656581] 高优先级任务
2018-03-16 18:33:53.437498+0800 test[6880:656579] 低优先级任务

为了比较、我们可以把依赖去掉。这样执行顺序就会按照代码添加的顺序执行了。

    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    
    operationQueue.maxConcurrentOperationCount = 1;
    
    NSBlockOperation *blockOperation1=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"低优先级任务");
    }];
    blockOperation1.queuePriority = NSOperationQueuePriorityLow;
    
    NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"高优先级任务");
        sleep(1);
    }];
    blockOperation2.queuePriority = NSOperationQueuePriorityHigh;

    
    [operationQueue addOperation:blockOperation1];
    [operationQueue addOperation:blockOperation2];

打印结果

2018-03-16 18:30:40.293723+0800 test[6825:651558] 低优先级任务
2018-03-16 18:30:40.294005+0800 test[6825:651555] 高优先级任务
阻塞
  • 操作阻塞- (void)waitUntilFinished;

阻塞当前线程、直到该操作执行完成。

    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
        
        sleep(3);
        NSLog(@"操作3执行完毕");
    }];
    
    NSBlockOperation *blockOperation2=[NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"操作2开始执行");
        [blockOperation3 waitUntilFinished];
        NSLog(@"操作2执行完毕");
    }];
    [operationQueue addOperation:blockOperation2];
    [operationQueue addOperation:blockOperation3];

打印结果

2018-03-19 10:40:11.856272+0800 test[1611:101411] 操作2开始执行
2018-03-19 10:40:14.857841+0800 test[1611:101413] 操作3执行完毕
2018-03-19 10:40:14.858028+0800 test[1611:101411] 操作2执行完毕
  • 队列阻塞- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
    • 如果为YES。阻塞当前线程、直到队列该次添加的所有操作全部执行完成。
    • 如果为NO。就是批量添加操作而已。
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    NSBlockOperation *blockOperation3=[NSBlockOperation blockOperationWithBlock:^{
        sleep(3);
        NSLog(@"操作3执行完毕");
    }];
    NSLog(@"添加操作");
    [operationQueue addOperations:@[blockOperation3] waitUntilFinished:YES];
    NSLog(@"添加完成");

打印结果:

2018-03-19 10:43:58.998232+0800 test[1690:108032] 添加操作
2018-03-19 10:44:02.000517+0800 test[1690:108155] 操作3执行完毕
2018-03-19 10:44:02.001007+0800 test[1690:108032] 添加完成
  • 线程/队列死锁:

这个不太好用语言描述。当阻塞生效时、若所依赖的操作无法并发完成。线程/队列将被锁死。举个例子:

    NSOperationQueue *operationQueue=[NSOperationQueue mainQueue];
    //pat1
    [operationQueue addOperations:@[blockOperation3] waitUntilFinished:YES];

    //blockOperation3的执行相当于被添加到最后
    //pat2
    blockOperation3();

最后的这个pat2将永远不会执行。因为主线程的并发为1、而这个1正在被占用。
pat1等待pat2执行完毕。pat2又在等待着pat2执行完毕。造成死锁。
如果operationQueue的并发数为2、pat2将会被放到另一个线程去执行、执行完毕解锁当前线程。就不会出现死锁的问题。

NSOperation的API

大概就一下这些东西、想看怎么用可以去开篇的帖子。或者自己搜搜。

  • 操作:

可以KVC的状态(取消、进行、准备就绪、完成)/阻塞线程/completion回调/移除依赖等等....

@interface NSOperation : NSObject {
@private
    id _private;
    int32_t _private1;
#if __LP64__
    int32_t _private1b;
#endif
}

// 开始操作
- (void)start;

// 操作任务的入口,一般用于自定义NSOperation的子类 
- (void)main;

// 判断是否已经被取消
@property (readonly, getter=isCancelled) BOOL cancelled;

// 取消操作Operation,调用后不会自动马上取消,需要通过isCancelled方法检查是否被取消,然后自己编写代码退出当前的Operation
- (void)cancel;

// 是否正在执行
@property (readonly, getter=isExecuting) BOOL executing;

// 是否执行完
@property (readonly, getter=isFinished) BOOL finished;

// 判定该线程是否是并发线程,即调用该operation的start方法的线程是否与operation所在线程相同
// 注意:此属性即将被弃用,之后使用asynchronous属性代替
@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below

// 是否异步执行
@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);

// 是否准备好
// 在start方法开始之前,需要确定Operation是否准备好,默认为YES,如果该operation没有准备好,则不会start。
@property (readonly, getter=isReady) BOOL ready;

// 添加依赖关系,如:[op1 addDependency:op2]; op2先执行,op1后执行  
- (void)addDependency:(NSOperation *)op;

// 取消依赖,注意:操作对象的依赖不能在操作队列执行时取消
- (void)removeDependency:(NSOperation *)op;

// 获取有依赖关系的Operation所组成的数组
@property (readonly, copy) NSArray<NSOperation *> *dependencies;

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

// 优先级
@property NSOperationQueuePriority queuePriority;

// Operation完成后调用的代码块
@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);

// 堵塞当前线程,直到该Operation执行结束,才会执行接下来的代码
- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);

// 设定Operation的线程优先级,取值范围0~1,默认为0.5
// 即使设定了线程优先级,也只能保证其在该线程的main()方法范围内有效,Operation的其他代码仍然执行在默认线程
@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);

// 用于系统自动合理的管理队列的资源分配。
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);

// 操作任务的名字
@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);

@end

需要注意的是如果一个操作的状态为取消、进行、完成。是不可以被添加进队列的。

  • 对于队列:
// 默认最大操作数为-1、也就是由系统分配
static const NSInteger NSOperationQueueDefaultMaxConcurrentOperationCount = -1;

@interface NSOperationQueue : NSObject {
@private
    id _private;
    void *_reserved;
}

// 添加操作对象(NSOperation对象)
- (void)addOperation:(NSOperation *)op;

// 添加操作对象组(NSOperation对象),waitUntilFinished:是否阻塞当前线程,等待所有操作都完成
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);

// 添加操作任务
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);

// 获取操作任务对象组
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;

// 获取操作任务对象总数
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);

// 最大并发数
@property NSInteger maxConcurrentOperationCount;

// 是否暂停队列
@property (getter=isSuspended) BOOL suspended;

// 队列名字
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);

// 用于系统自动合理的管理队列的资源分配
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);

// underlyingQueue属性的值是主线程的调度队列,此属性不能设置为其它值
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);

// 取消所有操作任务
- (void)cancelAllOperations;

// 阻塞当前线程,等待所有操作执行完毕 
- (void)waitUntilAllOperationsAreFinished;

// 获取当前操作队列
+ (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);

// 获取主操作队列
+ (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);

@end

最后

本文主要是自己的学习与总结。如果文内存在纰漏、万望留言斧正。如果不吝赐教小弟更加感谢。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容