NSOperationQueue
操作队列根据其优先级和准备情况执行排队的NSOperation
对象。 添加到操作队列后,操作将保留在其队列中,直到它报告已完成其任务。 添加后,您无法直接从队列中删除操作。
从多个线程使用单个NSOperationQueue
对象是安全的,没必要创建额外的 lock
, NSOperationQueue
类的属性都是默认atomic
的,关于锁和线程安全的内容在后续的文章中详叙
NSOperationQueue
都是和NSOperation
子类(NSInvocationOperation
或NSBlockOperation
)一起使用的,下面先介绍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
类是一个抽象类,所以不要直接使用它,而是使用系统定义的子类(NSInvocationOperation
或NSBlockOperation
)来执行实际任务。跟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
时,将执行completionBlock
。completionBlock
应该用于通知或执行可能与操作的实际任务相关但不属于该操作的其他任务。
在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