NSOperation類是iOS2.0推出的竞滓,通過NSThread實現(xiàn)的,但是效率一般。從iOS4推出GCD時又重寫了NSOperation和NSOperationQueue疫向,NSOperation和NSOperationQueue分別對應GCD的任務和隊列(了解GCD直通車:http://www.reibang.com/p/acc6e7bd6f10)咳蔚,所以NSOPeration和NSOperationQueue是基于GCD更高一層的封裝,而且完全地面向?qū)ο笊ν铡5潜菺CD更簡單易用谈火、代碼可讀性也更高。NSOperation和NSOperationQueue對比GCD會帶來一點額外的系統(tǒng)開銷舌涨,但是可以在多個操作Operation中添加附屬糯耍。
優(yōu)點
- 可添加完成的代碼塊,在操作完成后執(zhí)行囊嘉。
- 添加操作之間的依賴關系温技,方便的控制執(zhí)行順序。
- 設定操作執(zhí)行的優(yōu)先級哗伯。
- 可以很方便的取消一個操作的執(zhí)行荒揣。
- 使用 KVO 觀察對操作執(zhí)行狀態(tài)的更改:isExecuteing、isFinished焊刹、isCancelled系任。
NSOperation、NSOperationQueue
NSOperation是一個和任務相關的抽象類虐块,不具備封裝操作的能力俩滥,必須使用其子類:NSInvocationOperation或者NSBlockOperation,當然也可以自定義子類,實現(xiàn)內(nèi)部相應的?法贺奠,NSOperation實例在多線程上執(zhí)行是線程安全的不需要添加額外的鎖霜旧,不 必管理線程生命周期和同步等問題。NSInvocationOperation 和NSBlockOperation子類不同的是儡率,因為NSInvocationOperation沒有額外添加任務的方法挂据,所以使用NSInvocationOperation創(chuàng)建的對象只會有一個任務,其次使用NSBlockOperation來執(zhí)行任務切任務大于1的時候儿普,系統(tǒng)可能會開辟新線程來異步執(zhí)行任務崎逃。
NSOperationQueue有主隊列和自定義隊列(串行和并發(fā)),將NSOperation對象添加NSOperationQueue中眉孩,該NSOperationQueue對象從線程中拿取操作个绍、以及分配到對應線程的工作都是由系統(tǒng)處理的勒葱。一般操作對象添加到NSOperationQueue之后,如果不存在依賴或者整個隊列被暫停情況下通常短時間就開始運行巴柿。NSOperationQueue可以通過對象屬性suspended
來決定是否讓隊列暫時停止對任務的調(diào)度凛虽,或者cancel、cancelAllOperations
方法來取消操作對象广恢,不過使用這兩個方法后操作對象無法恢復凯旋,操作時只會停止調(diào)度隊列中操作對象(注意:正在執(zhí)行的操作依然會執(zhí)行,無法取消袁波。 )瓦阐, 且取消不可恢復。
首先創(chuàng)建一個NSOperation的子類(以NSInvocationOperation為例)篷牌,再創(chuàng)建隊列NSOperationQueue睡蟋,最后將操作加入隊列。這樣我們就完成了多線程的操作枷颊〈辽保可以直接執(zhí)行start
方法,但不會開辟新線程去執(zhí)行操作夭苗,而是在當前線程同步執(zhí)行任務信卡。這里注意,如果將操作添加到隊列的同時再次執(zhí)行start
题造,這時會拋出異常傍菇,因為線程在創(chuàng)建后,開始進入Runnable就緒的狀態(tài)界赔,如果此時再次執(zhí)行start
會重復初始化操作丢习。
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"123"];
// 將op加入到隊列中
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op];
[[NSOperationQueue mainQueue] addOperation:op];
// 或者直接start
[op start];
因為基于GCD更高一層的封裝,NSOperation同樣也具備線程之間的通訊以及控制并發(fā)數(shù)淮悼,舉個簡單的例子:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 2;
for (int i = 0; i<10; i++) {
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"%d-%@",i,[NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"%d-%@--%@",i,[NSOperationQueue currentQueue],[NSThread currentThread]);
}];
}];
}
由于設置了最大并發(fā)數(shù)maxConcurrentOperationCount
為2咐低,所以每2秒輸出四個任務。
0-<NSThread: 0x600003f08180>{number = 4, name = (null)}
1-<NSThread: 0x600003f17bc0>{number = 5, name = (null)}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
2-<NSThread: 0x600003f31e00>{number = 7, name = (null)}
3-<NSThread: 0x600003f08180>{number = 4, name = (null)}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
... // 省略部分相似打印信息
9-<NSThread: 0x600003f08180>{number = 4, name = (null)}
8-<NSThread: 0x600003f17bc0>{number = 5, name = (null)}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
<NSOperationQueue: 0x7fce0de147f0>{name = 'NSOperationQueue Main Queue'} --<NSThread: 0x600003f5acc0>{number = 1, name = main}
當然我們也可以自定義子類袜腥,可能會添加到串行和并發(fā)隊列的不同情況见擦,需要重寫不同的方法。TIP:并發(fā)操作時羹令,需要自己創(chuàng)建自動釋放池鲤屡,因為異步操作無法訪問主線程的自動釋放池。經(jīng)常通過cancelled屬性檢查方法是否取消福侈,并且對取消做出響應酒来。
操作依賴
NSOperation、NSOperationQueue 最吸引人的地方是它能添加操作之間的依賴關系癌刽,可以使用依賴關系來控制操作間的啟動順序役首。當一個操作對象添加了依賴显拜,被依賴的操作對象就會先執(zhí)行衡奥,當被依賴的操作對象執(zhí)行完才會當前的操作對象,通過操作依賴可以很方便的按照特定順序控制操作之間的執(zhí)行先后順序远荠。操作對象可以通過addDependency
添加和removeDependency
移除依賴矮固。在添加線程對象NSOperationQueue之前進行依賴設置,操作對象會管理自己的依賴譬淳,因此在不相同隊列中的操作對象可以建立依賴關系档址。
舉例:現(xiàn)在有任務1、2邻梆、3守伸,當任務1執(zhí)行完畢后再執(zhí)行任務2,任務2執(zhí)行完畢后再執(zhí)行任務3浦妄。
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"任務----1");
}];
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"任務----2");
}];
NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"任務----3");
}];
// 建立依賴
[bo2 addDependency:bo1];
[bo3 addDependency:bo2];
[self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@"執(zhí)行結(jié)束");
這里waitUntilFinished
如果設置為YES尼摹,則會堵塞當前線程,直到該操作結(jié)束剂娄。
最終打印效果總是:任務1->任務2->任務3蠢涝。
任務----1
任務----2
任務----3
執(zhí)行結(jié)束
優(yōu)先級、服務質(zhì)量
NSOperation 提供了queuePriority(優(yōu)先級)屬性阅懦,queuePriority屬性適用于同一操作隊列中的操作和二,不適用于不同操作隊列中的操作。默認情況下耳胎,所有新創(chuàng)建的操作對象優(yōu)先級都是NSOperationQueuePriorityNormal惯吕。但是我們可以通過
setQueuePriority
方法來改變當前操作在同一隊列中的執(zhí)行優(yōu)先級。在iOS 8.0后场晶,推出了服務質(zhì)量混埠,通過設置服務質(zhì)量讓系統(tǒng)優(yōu)先處理某一個操作。
NSOperation優(yōu)先級的枚舉和Quality of Service枚舉:
// NSOperation.h
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};
// ----------------------------
// NSObjCRuntime.h
typedef NS_ENUM(NSInteger, NSQualityOfService) {
//與用戶交互的任務诗轻,這些任務通常跟UI級別的刷新相關钳宪,比如動畫,這些任務需要在一瞬間完成.
NSQualityOfServiceUserInteractive = 0x21,
// 由用戶發(fā)起的并且需要立即得到結(jié)果的任務扳炬,比如滑動scroll view時去加載數(shù)據(jù)用于后續(xù)cell的顯示吏颖,這些任務通常跟后續(xù)的用戶交互相關,在幾秒或者更短的時間內(nèi)完成
NSQualityOfServiceUserInitiated = 0x19,
// 一些可能需要花點時間的任務恨樟,這些任務不需要馬上返回結(jié)果半醉,比如下載的任務,這些任務可能花費幾秒或者幾分鐘的時間
NSQualityOfServiceUtility = 0x11,
// 一些可能需要花點時間的任務劝术,這些任務不需要馬上返回結(jié)果
NSQualityOfServiceBackground = 0x09,
// 一些可能需要花點時間的任務缩多,這些任務不需要馬上返回結(jié)果
NSQualityOfServiceDefault = -1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
Utility 及以下的優(yōu)先級會受到 iOS9 中低電量模式的控制呆奕,另外在沒有用戶操作時,90% 任務的優(yōu)先級都應該在 Utility 之下衬吆。
一般對于添加到隊列中的任務梁钾,當這個任務的所有依賴都已經(jīng)完成時,任務通常會進入準備就緒狀態(tài)來等待執(zhí)行逊抡,這時該任務的開始執(zhí)行順序由任務的優(yōu)先級決定姆泻。如果一個隊列中既包含高優(yōu)先級就緒狀態(tài)的任務,又包含低優(yōu)先級就緒狀態(tài)的任務冒嫡,高優(yōu)先級就緒狀態(tài)的任務會優(yōu)先被執(zhí)行拇勃;
服務質(zhì)量則根據(jù)CPU,網(wǎng)絡和磁盤的分配來創(chuàng)建一個操作的系統(tǒng)優(yōu)先級孝凌。一個高質(zhì)量的服務意味著可以提供更多的資源來更快的完成操作方咆,涉及到CPU調(diào)度的優(yōu)先級、IO優(yōu)先級蟀架、任務運行所在的線程以及運行的順序等等峻呛。正確的指定線程或任務優(yōu)先級可以讓系統(tǒng)更加智能的管理隊列的資源分配,以便于提高執(zhí)行效率和控制電量等辜窑。
自定義NSOperation子類
當NSInvocationOperation或者NSBlockOperation無法滿足我們?nèi)粘P枨蠊呈觯覀円部梢远x串行和并發(fā)的2種類型的NSOperation子類,注意需要創(chuàng)建自動釋放池穆碎,異步操作無法訪問主線程的自動釋放池牙勘。在自定義串行NSOperation子類時要重寫main
方法并且最好添加一個init方法用于初始化數(shù)據(jù),在自定義并行NSOperation子類是需要重寫start所禀、isFinished方面、isAsynchronous
和isExecuting
方法。經(jīng)常通過cancelled屬性檢查方法是否取消色徘,并且對取消的做出響應恭金,定期調(diào)用對象的isCancelled方法,如果返回“YES”褂策,則立即返回横腿,不再執(zhí)行任務。
如果想進一步了解自定義NSOperation子類的具體實現(xiàn)斤寂,接下來將利用自定義NSOperation子類耿焊,同時借鑒了AFNetworking、SDWebImage遍搞、YYKit
的部分思想來實現(xiàn)具有緩存支持的異步圖像下載器罗侯。
該文章為記錄本人的學習路程,也希望能夠幫助大家溪猿,知識共享钩杰,共同成長纫塌,共同進步!=才护戳!