1 簡單使用 NSOperationQueue
上一篇文章中看到使用自定義NSOperation來實(shí)現(xiàn)多線程,寫法有些復(fù)雜,但其實(shí),使用NSOperationQueue來實(shí)現(xiàn)多線程非常簡單
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建3個(gè) NSInvocationOperation 操作
NSOperationQueue *opQueue = [NSOperationQueue new];
for (NSUInteger i = 0; i < 3; i++) {
// 可以傳遞一個(gè) NSObject 給operation的操作方法
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Operation_%lu", i] forKey:@"key"];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationSelector:) object:dict];
[opQueue addOperation:op];
}
}
// NSInvocationOperation 操作執(zhí)行的方法
- (void)operationSelector:(NSDictionary *)dict
{
// 接收傳進(jìn)來的dict
NSLog(@"dictValue = %@", [dict valueForKey:@"key"]);
sleep(10); // 加個(gè)睡眠模仿耗時(shí)操作
NSLog(@"currentThread = %@", [NSThread currentThread]);
NSLog(@"mainThread = %@", [NSThread mainThread]);
}
控制臺(tái)輸出結(jié)果為:
2016-02-25 16:58:18.282 test[57194:18487531] dictValue = Operation_0
2016-02-25 16:58:18.282 test[57194:18487530] dictValue = Operation_1
2016-02-25 16:58:18.282 test[57194:18487533] dictValue = Operation_2
2016-02-25 16:58:28.283 test[57194:18487530] currentThread = <NSThread: 0x7fbf40435bb0>{number = 2, name = (null)}
2016-02-25 16:58:28.284 test[57194:18487531] currentThread = <NSThread: 0x7fbf4050f2a0>{number = 3, name = (null)}
2016-02-25 16:58:28.284 test[57194:18487533] currentThread = <NSThread: 0x7fbf4290f560>{number = 4, name = (null)}
2016-02-25 16:58:28.284 test[57194:18487530] mainThread = <NSThread: 0x7fbf405058c0>{number = 1, name = (null)}
2016-02-25 16:58:28.284 test[57194:18487531] mainThread = <NSThread: 0x7fbf405058c0>{number = 1, name = (null)}
2016-02-25 16:58:28.284 test[57194:18487533] mainThread = <NSThread: 0x7fbf405058c0>{number = 1, name = (null)}
可以看出來這三個(gè)操作是并發(fā)執(zhí)行的,而且都不在主線程中執(zhí)行。
2 NSOperationQueue 的其他屬性
添加操作有3個(gè)方法:
// 直接添加一個(gè) NSOperation 操作,并且加入并發(fā)隊(duì)列,只要當(dāng)前隊(duì)列允許,就會(huì)立刻執(zhí)行。
- (void)addOperation:(NSOperation *)op;
// 添加一組操作,如果 waitUntilFinished 為 NO,則必須在當(dāng)前隊(duì)列中的所有操作都執(zhí)行完了,才會(huì)執(zhí)行這組操作,否則立刻執(zhí)行。
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);
// 直接在這里寫一個(gè)block,block中的操作加入并發(fā)隊(duì)列,并且只要當(dāng)前隊(duì)列允許執(zhí)行,就會(huì)立刻執(zhí)行。
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
接下來看其他的屬性
// 返回當(dāng)前隊(duì)列中的所有操作NSOperation
@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;
// 返回當(dāng)前隊(duì)列中的操作數(shù)量,對(duì)應(yīng) operations.count
@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);
// 可讀寫的屬性,當(dāng)設(shè)備性能不足或根據(jù)需求要限制并行的操作數(shù)量時(shí),可以設(shè)置這個(gè)值。
// 設(shè)置了這個(gè)值之后,隊(duì)列中并發(fā)執(zhí)行的操作數(shù)量不會(huì)大于這個(gè)值。超出這個(gè)值在排隊(duì)中的操作會(huì)處于休眠狀態(tài)。
// 默認(rèn)值為 NSOperationQueueDefaultMaxConcurrentOperationCount = -1
@property NSInteger maxConcurrentOperationCount;
// 可以給隊(duì)列指定一個(gè)名字用來做標(biāo)識(shí)
@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);
// 給隊(duì)列指定一個(gè)優(yōu)先級(jí),默認(rèn)為 NSQualityOfServiceDefault = -1
@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);
// ??? 這個(gè)不是太理解
@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);
// 取消隊(duì)列中的所有操作。其實(shí)就是調(diào)用 operations 中每個(gè)操作的`cancel`方法才取消操作。
// 但是,在前面的文章中說過,調(diào)用`cancel`方法并不會(huì)終止操作,而是設(shè)置`cancelled`屬性為 YES,
// 這就需要自己在操作中分節(jié)點(diǎn)去判斷`cancelled`屬性了,在適當(dāng)?shù)臅r(shí)機(jī)結(jié)束操作。
- (void)cancelAllOperations;
// 調(diào)用這個(gè)方法時(shí),會(huì)判斷 NSOperationQueue 中的操作是否全部執(zhí)行完,如果沒有,則調(diào)用者所在的線程會(huì)在調(diào)用處等待。
// 直到 NSOperationQueue 中的所有操作執(zhí)行完成,當(dāng)前線程才繼續(xù)執(zhí)行。如果 NSOperationQueue 為空,則該方法立刻返回。
- (void)waitUntilAllOperationsAreFinished;
// 取得調(diào)用者的當(dāng)前線程中的 NSOperationQueue 操作隊(duì)列
+ (nullable NSOperationQueue *)currentQueue NS_AVAILABLE(10_6, 4_0);
// 取得主線程中的
+ (NSOperationQueue *)mainQueue NS_AVAILABLE(10_6, 4_0);
@property (getter=isSuspended) BOOL suspended;
這個(gè)值很有意思,從字面意思理解是暫停隊(duì)列,但是怎么個(gè)暫停呢?從官方文檔上看
**Discussion**
When the value of this property is NO, the queue actively starts operations that are in the queue and ready to execute. Setting this property to YES prevents the queue from starting any queued operations, but already executing operations continue to execute. You may continue to add operations to a queue that is suspended but those operations are not scheduled for execution until you change this property to NO.
Operations are removed from the queue only when they finish executing. However, in order to finish executing, an operation must first be started. Because a suspended queue does not start any new operations, it does not remove any operations (including cancelled operations) that are currently queued and not executing.
You may monitor changes to the value of this property using key-value observing. Configure an observer to monitor the suspended key path of the operation queue.
The default value of this property is NO.
大概翻譯一下,如果這個(gè)值設(shè)置為 NO,那說明這個(gè)隊(duì)列已經(jīng)準(zhǔn)備好了可以執(zhí)行了。如果這個(gè)值設(shè)置為 YES,那么已經(jīng)添加到隊(duì)列中的操作還是可以執(zhí)行了,而后面繼續(xù)添加進(jìn)隊(duì)列中的操作才處于暫停狀態(tài),直到你再次將這個(gè)值設(shè)置為 NO 時(shí),后面加入的操作才會(huì)繼續(xù)執(zhí)行。這個(gè)屬性的默認(rèn)值是 NO。
來看一下使用的方法例子:
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建3個(gè) NSInvocationOperation 操作
_opQueue = [NSOperationQueue new];
for (NSUInteger i = 0; i < 3; i++) {
// 可以傳遞一個(gè) NSObject 給operation的操作方法
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Operation_%lu", i] forKey:@"key"];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationSelector:) object:dict];
[_opQueue addOperation:op];
}
// 這里設(shè)置為 YES
_opQueue.suspended = YES;
// 然后再添加一個(gè)操作,序號(hào)為 9
NSDictionary *dict = [NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"Operation_%d", 9] forKey:@"key"];
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationSelector:) object:dict];
[_opQueue addOperation:op];
}
// NSInvocationOperation 操作執(zhí)行的方法
- (void)operationSelector:(NSDictionary *)dict
{
// 接收傳進(jìn)來的dict
NSLog(@"dictValue = %@", [dict valueForKey:@"key"]);
sleep(10); // 加個(gè)睡眠模仿耗時(shí)操作
NSLog(@"currentThread = %@", [NSThread currentThread]);
NSLog(@"mainThread = %@", [NSThread mainThread]);
// 執(zhí)行完其中一個(gè)操作之后把 suspended 改為 NO。
_opQueue.suspended = NO;
}
2016-02-25 17:22:07.546 test[57547:18605364] dictValue = Operation_2
2016-02-25 17:22:07.546 test[57547:18605360] dictValue = Operation_0
2016-02-25 17:22:07.546 test[57547:18605361] dictValue = Operation_1
2016-02-25 17:22:10.547 test[57547:18605361] currentThread = <NSThread: 0x7ff598d07b00>{number = 3, name = (null)}
2016-02-25 17:22:10.547 test[57547:18605364] currentThread = <NSThread: 0x7ff59a9784f0>{number = 2, name = (null)}
2016-02-25 17:22:10.547 test[57547:18605360] currentThread = <NSThread: 0x7ff59aa05100>{number = 4, name = (null)}
2016-02-25 17:22:10.547 test[57547:18605364] mainThread = <NSThread: 0x7ff598c08770>{number = 1, name = (null)}
2016-02-25 17:22:10.547 test[57547:18605360] mainThread = <NSThread: 0x7ff598c08770>{number = 1, name = (null)}
2016-02-25 17:22:10.547 test[57547:18605361] mainThread = <NSThread: 0x7ff598c08770>{number = 1, name = (null)}
2016-02-25 17:22:10.547 test[57547:18605513] dictValue = Operation_9
2016-02-25 17:22:13.620 test[57547:18605513] currentThread = <NSThread: 0x7ff598c08ce0>{number = 5, name = (null)}
2016-02-25 17:22:13.620 test[57547:18605513] mainThread = <NSThread: 0x7ff598c08770>{number = 1, name = (null)}
可以看出來,操作9是在suspended改為 NO 之后才開始執(zhí)行的。
最后:以上很多屬性都支持 KVO ,可以通過監(jiān)聽某個(gè)值的變化來做不同的操作,這里就不贅述了。
3 總結(jié)
NSOperationQueue為我們提供了非常簡便的使用多線程的方法,如果需要使用NSOperation,則更多建議使用NSOperationQueue而不是自定義NSOperation。