1 簡(jiǎn)單使用 NSOperationQueue
上一篇文章中看到使用自定義NSOperation
來(lái)實(shí)現(xiàn)多線程靠柑,寫(xiě)法有些復(fù)雜,但其實(shí)吓懈,使用NSOperationQueue
來(lái)實(shí)現(xiàn)多線程非常簡(jiǎ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)來(lái)的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)}
可以看出來(lái)這三個(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);
// 直接在這里寫(xiě)一個(gè)block募判,block中的操作加入并發(fā)隊(duì)列荡含,并且只要當(dāng)前隊(duì)列允許執(zhí)行,就會(huì)立刻執(zhí)行届垫。
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
接下來(lái)看其他的屬性
// 返回當(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);
// 可讀寫(xiě)的屬性,當(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è)名字用來(lái)做標(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`方法才取消操作躺盛。
// 但是,在前面的文章中說(shuō)過(guò)形帮,調(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í)行完合冀,如果沒(méi)有各薇,則調(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完残,那說(shuō)明這個(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。
來(lái)看一下使用的方法例子:
- (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)來(lái)的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)}
可以看出來(lái),操作9是在suspended
改為 NO 之后才開(kāi)始執(zhí)行的亡笑。
最后:以上很多屬性都支持 KVO 侣夷,可以通過(guò)監(jiān)聽(tīng)某個(gè)值的變化來(lái)做不同的操作,這里就不贅述了仑乌。
3 總結(jié)
NSOperationQueue為我們提供了非常簡(jiǎn)便的使用多線程的方法百拓,如果需要使用NSOperation
,則更多建議使用NSOperationQueue
而不是自定義NSOperation
晰甚。