iOS多线程之NSOperationQueue、NSOperation

NSOperationQueue操作队列根据其优先级和准备情况执行排队的NSOperation对象。 添加到操作队列后,操作将保留在其队列中,直到它报告已完成其任务。 添加后,您无法直接从队列中删除操作。
从多个线程使用单个NSOperationQueue对象是安全的,没必要创建额外的 lock, NSOperationQueue类的属性都是默认atomic的,关于锁和线程安全的内容在后续的文章中详叙
NSOperationQueue都是和NSOperation子类(NSInvocationOperationNSBlockOperation)一起使用的,下面先介绍NSOperationQueue

NSOperationQueue常用属性和方法:

1. mainQueue

返回与主线程关联的操作队列。

2. currentQueue

返回当前操作的操作队列。

3. name

操作队列名称,可用于运行时标识操作队列,此属性的默认值是包含操作队列的内存地址的字符串。

4. maxConcurrentOperationCount

可同时进行的最大操作数,设置为1相当于串行队列,运行时减少并发操作的数量不会影响当前正在执行的任何操作。 指定值(也是默认值)NSOperationQueueDefaultMaxConcurrentOperationCount(-1)会导致系统根据系统条件设置最大操作数。(比如根据 CPU 最大核心数,这个数据可用[NSProcessInfo processInfo].activeProcessorCount获得)

5. operations

返回当前在队列的所有操作,此属性中的数组按其添加到队列的顺序返回。 此顺序不一定代表执行这些操作的顺序。

6. operationCount

返回当前在队列的操作的数量,返回的值只表示访问该属性时的临时状态,因为队列中的操作数会随着操作的完成而更改,因此,请勿将此值用于operations属性的枚举等

6. qualityOfService

应用于使用队列执行的操作的默认优先级。如果添加到队列的operation有自己的优先级,则使用operation的优先级;对于您自己创建的队列,默认值为NSOperationQualityOfServiceBackground。 对于mainQueue方法返回的队列,默认值为NSOperationQualityOfServiceUserInteractive,无法更改。

7. suspended

布尔值,指示队列是否是暂停状态。将此属性设置为YES可防止队列启动任何排队操作,但已执行的操作将继续执行。 您可以继续向已暂停的队列添加操作,但在将此属性更改为NO之前,不会执行这些操作。

8. underlyingQueue

用于执行队列的操作,默认为nil,仅当队列中没有操作时,才可以设置此属性的值; 当operationCount不等于0时设置此属性的值会引发NSInvalidArgumentException。 此属性的值不能设置成主线程。 此队列设置的优先级别将覆盖为操作队列的qualityOfService属性设置的任何值。

9. addOperation

添加一个操作到队列中,操作对象一次最多只能在一个操作队列中,如果操作已经在另一个队列中,则此方法抛出NSInvalidArgumentException异常。 同样,如果操作当前正在执行或已经完成执行,则此方法抛出NSInvalidArgumentException异常。

10. addOperationWithBlock

将一个无参数无返回值的block包装成对象,然后添加到队列中; 您不应尝试获取对新创建的操作对象的引用或确定其类型信息。

11. addOperations:waitUntilFinished:
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;

将操作数组Operations添加到队列中,其中wait:如果 是YES,则阻止当前线程,直到所有操作完成执行。 如果为NO,立即返回。

NSOperation

因为NSOperation类是一个抽象类,所以不要直接使用它,而是使用系统定义的子类(NSInvocationOperationNSBlockOperation)来执行实际任务。跟NSOperationQueue一样,可以安全地从多个线程调用NSOperation对象的方法,而无需创建额外的锁来同步对象的访问。

1. isReady

isReady表示操作何时可以执行。 当操作准备好立即执行时,值为YES

2. isExecuting

如果操作正在处理其任务,则isExecuting属性返回YES,否则返回NO

3. isFinished

isFinished表示操作已成功完成任务或已取消并正在退出。 在isFinished的值更改为YES之前,操作对象不会清除依赖关系。 类似地,在isFinished属性包含值YES之前,操作队列不会使操作出列。

4. isCancelled

isCancelled表示操作是否调用了取消方法。
将操作添加到队列后,如果你稍后决定不想执行操作 ( 比如用户在页面中按下取消按钮或退出应用程序,这时可以取消操作以防止它不必要地消耗CPU时间)。 您可以通过调用操作对象本身的cancel方法或通过调用NSOperationQueue类的cancelAllOperations方法来完成取消操作。
取消操作不会立即停止正在进行的操作。 你的代码必须检查此属性中的值并根据需要中止操作。 NSOperation的默认实现包括检查取消, 例如,如果在调用start方法之前取消操作,则start方法退出而不启动任务。

5. start

此方法的默认实现更新操作的执行状态并调用main方法。 此方法还执行多项检查以确保操作可以实际运行。 例如,如果操作被取消或已经完成,则此方法只返回而不调用main。如果操作当前正在执行或未准备好执行(isReady),则此方法将引发NSInvalidArgumentException异常。

6. completionBlock

操作任务完成后执行的块。此Block块不带参数,也没有返回值。不应该使用此Block块来执行任何需要特定上下文的工作。 相反,你应该将该工作分流到应用程序的主线程或能够执行此操作的特定线程。
finished属性中的值更改为YES时,将执行completionBlockcompletionBlock应该用于通知或执行可能与操作的实际任务相关但不属于该操作的其他任务。
在iOS 8后,在completionBlock开始执行后,此属性设置为nil

7. addDependency、 removeDependency、dependencies
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;

@property (readonly, copy) NSArray<NSOperation *> *dependencies;
  • addDependency:使当前操作的执行依赖于op的完成(取消操作同样将其标记为已完成)。在所有依赖操作完成执行之前,当前操作不被认为准备好执行(isReady)。 如果当前操作已经在执行其任务,则添加依赖项没有实际效果。 另外,不要写出循环依赖的代码。
  • removeDependency:删除对指定操作的依赖性。
  • dependencies:依赖的操作对象数组,在所有依赖操作完成执行之前,当前操作对象不得执行。 在依赖的操作完成执行时,不会从此dependencies中删除操作。 您可以使用此列表来跟踪所有的依赖操作,包括那些已经完成执行的操作。 从此列表中删除操作的唯一方法是使用removeDependency:方法。
8. qualityOfService

当前操作的优先级,默认值为NSQualityOfServiceBackground,修改后,此值会覆盖NSOperationQueue的优先级

9. queuePriority

表示操作队列中操作的执行相对优先级。 该值用于影响操作出列和执行的顺序。 默认NSOperationQueuePriorityNormal。不要使用queuePriority来实现不同操作对象之间的依赖关系管理(因为就算 操作A 的queuePriority高于操作 B,也不能保证执行完操作A 再执行操作 B)。 所以如果需要在操作之间建立依赖关系,请使用addDependency:方法。

10. waitUntilFinished

阻止当前线程的执行,直到操作对象完成其任务。因为会阻塞当前线程,所以不要在主线程调用,更不要在当前操作的operationQueue上调用,这样会导致死锁。
代码如下:

    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block Operation before");
        [NSThread sleepForTimeInterval:2.];
        NSLog(@"block Operation end");
    }];
    [queue addOperation:blockOperation];
    dispatch_async(globalQueue, ^{
        NSLog(@"block Operation wait");
        [blockOperation waitUntilFinished];
        NSLog(@"block Operation done");
    });

执行结果:

2018-11-09 14:20:27.667928+0800 YMultiThreadDemo[1868:242200] block Operation wait
2018-11-09 14:20:27.667934+0800 YMultiThreadDemo[1868:242199] block Operation before
2018-11-09 14:20:29.672049+0800 YMultiThreadDemo[1868:242199] block Operation end
2018-11-09 14:20:29.672413+0800 YMultiThreadDemo[1868:242200] block Operation done

NSInvocationOperation

- (instancetype)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

target:定义sel的对象
sel:运行操作时要调用的选择器。 可以采用0或1个参数; 如果它接受参数,则该参数的类型必须为id。 方法的返回类型可以是void,或可以作为id类型返回的对象。返回值通过类的result属性获取。
arg:要传递给sel的参数对象。 如果没有参数,设置为nil
下面的代码有参数,有返回值,因为获取返回值需确定操作已经执行完成,所以使用waitUntilFinished方法:

    NSString *string = @"abcdefg";
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(changeString:) object:string];
    [queue addOperation:invocationOperation];
    dispatch_async(globalQueue, ^{
        [invocationOperation waitUntilFinished];
        NSLog(@"invocation Operation result:%@",invocationOperation.result);
    });
- (NSString *)changeString:(NSString *)string{
    NSLog(@"changeString before");
    [NSThread sleepForTimeInterval:1.];
    NSLog(@"changeString end");
    return string.capitalizedString;
}

执行结果:

2018-11-09 15:07:32.062420+0800 YMultiThreadDemo[2305:292761] changeString before
2018-11-09 15:07:33.066702+0800 YMultiThreadDemo[2305:292761] changeString end
2018-11-09 15:07:33.067063+0800 YMultiThreadDemo[2305:292759] invocation Operation result:Abcdefg

NSBlockOperation

NSBlockOperation类是NSOperation的具体子类,它管理一个或多个块的并发执行。 您可以使用此对象一次执行多个块,而无需为每个块创建单独的操作对象。 当执行多个块时,仅当所有块都已完成执行时,才认为操作本身已完成。

+ (instancetype)blockOperationWithBlock:(void (^)(void))block;

- (void)addExecutionBlock:(void (^)(void))block;
@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;

blockOperationWithBlock:创建并返回NSBlockOperation对象,并将指定的block块添加到该对象。
addExecutionBlock:将指定的block块添加到对象要执行的块列表中。在对象正在执行或已经完成时调用此方法会导致抛出NSInvalidArgumentException异常。
测试代码:

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block Operation before");
        [NSThread sleepForTimeInterval:2.];
        NSLog(@"block Operation end");
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block2 Operation before");
        [NSThread sleepForTimeInterval:1.];
        NSLog(@"block2 Operation end");
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block3 Operation before");
        [NSThread sleepForTimeInterval:3.];
        NSLog(@"block3 Operation end");
    }];
    [queue addOperation:blockOperation];
    
    dispatch_async(globalQueue, ^{
        NSLog(@"block Operation wait");
        [blockOperation waitUntilFinished];
        NSLog(@"block all Operation done");
    });

执行结果如下,可以看出blockOperation是并行的,且所有block执行完成才算blockOperation完成

2018-11-09 15:31:35.807683+0800 YMultiThreadDemo[2563:321503] block Operation wait
2018-11-09 15:31:35.807760+0800 YMultiThreadDemo[2563:321504] block Operation before
2018-11-09 15:31:35.807807+0800 YMultiThreadDemo[2563:321506] block2 Operation before
2018-11-09 15:31:35.807825+0800 YMultiThreadDemo[2563:321505] block3 Operation before
2018-11-09 15:31:36.809055+0800 YMultiThreadDemo[2563:321506] block2 Operation end
2018-11-09 15:31:37.809152+0800 YMultiThreadDemo[2563:321504] block Operation end
2018-11-09 15:31:38.812710+0800 YMultiThreadDemo[2563:321505] block3 Operation end
2018-11-09 15:31:38.812916+0800 YMultiThreadDemo[2563:321503] block all Operation done

参考文献

https://developer.apple.com/documentation/foundation/nsoperation?language=objc

https://developer.apple.com/documentation/foundation/nsoperationqueue?language=objc

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

推荐阅读更多精彩内容