-
基本概念
- 術(shù)語
- 串行 vs 并發(fā)(concurrency)
- 同步 vs 異步
- 隊列 vs 線程
iOS的并發(fā)編程模型
Operation Queues vs. Grand Central Dispatch (GCD)
-
關(guān)于Operation對象
- 并發(fā)的Operation 和非并發(fā)的Operation
- 創(chuàng)建NSBlockOperation對象
- 創(chuàng)建NSInvocationOperation對象
-
自定義Operation對象
- 自定義的非并發(fā)NSOperation-不實現(xiàn)取消操作
- 自定義的非并發(fā)NSOperation-實現(xiàn)取消操作
-
定制Operation對象的執(zhí)行行為
- 修改Operation在隊列中的優(yōu)先級
- 修改Operation執(zhí)行任務(wù)線程的優(yōu)先級
- 設(shè)置Completion Block
-
執(zhí)行Operation對象
- 添加Operation到Operation Queue中
- 手動執(zhí)行Operation
- 取消Operation
- 等待Operation執(zhí)行完成
- 暫停和恢復(fù)Operation Queue
添加Operation Queue中Operation對象之間的依賴
總結(jié)
看過上面的結(jié)構(gòu)預(yù)覽磨镶,下面就開始我們這篇blog
術(shù)語
Operation: The NSOperation class is an abstract class you use to encapsulate the code and data associated with a single task.
解釋:Operation是一個抽象類囊拜。你可以通過組織一段代碼和數(shù)據(jù),表示一個任務(wù)商模。
Operation Queue: The NSOperationQueue class regulates the execution of a set of NSOperation objects.
解釋: NSOperationQueue用于規(guī)則的去執(zhí)行一系列Operation。
任務(wù):通常的說是由一段代碼和數(shù)據(jù)組成做瞪,可以完成特定某項功能的代碼數(shù)據(jù)集合契讲。
進(jìn)程:進(jìn)程可以理解CPU所能執(zhí)行的單個任務(wù),CPU任何一個時刻職能運行一個進(jìn)程鹃栽。
線程:線程是計算機(jī)CPU所能執(zhí)行最小單元躏率,亦可以理解簡化版的進(jìn)程躯畴。一個進(jìn)程可以包含多個線程。
串行 vs 并發(fā)
最簡單的理解就是薇芝,串行和并發(fā)是用來修飾是否可以同時執(zhí)行任務(wù)的數(shù)量的蓬抄。串行設(shè)計只允許同一個時間段中只能一個任務(wù)在執(zhí)行。并發(fā)設(shè)計在同一個時間段中夯到,允許多個任務(wù)在邏輯上交織進(jìn)行嚷缭。(在iOS中,串行和并發(fā)一般用于描述隊列)
說個題外話耍贾,剛開始是將并發(fā)寫成并行的阅爽,后覺得并發(fā)和并行的概念一直揮之不去,可以參考這篇荐开,很贊奧——還在疑惑并發(fā)和并行付翁?
同步 vs 異步
同步操作,只有當(dāng)該操作執(zhí)行完成返回后晃听,才能執(zhí)行其他代碼百侧,會出現(xiàn)等待,易造成線程阻塞能扒。異步操作佣渴,不需要等到當(dāng)前操作執(zhí)行完,就可以返回初斑,執(zhí)行其他代碼辛润。(一般用于描述線程)
隊列 vs 線程
隊列用于存放Operation。在iOS中见秤,隊列分為串行隊列和并發(fā)隊列砂竖。使用NSOperationQueue時,我們不需要自己創(chuàng)建去創(chuàng)建線程鹃答,我們只需要自己去創(chuàng)建我們的任務(wù)(Operation)晦溪,將Operation放到隊列中去。隊列會負(fù)責(zé)去創(chuàng)建線程執(zhí)行挣跋,執(zhí)行完后三圆,會銷毀釋放線程占用的資源。
iOS并發(fā)編程模型
對于一個APP避咆,需要提高應(yīng)用的性能舟肉,一般需要創(chuàng)建輔助的線程去執(zhí)行任務(wù)。在整個APP的生命周期內(nèi)查库,我們需要自己手動去創(chuàng)建路媚,銷毀線程,以及暫停樊销,開啟線程整慎。對于這創(chuàng)建一個這樣的線程管理方案脏款,已經(jīng)是非常復(fù)雜且艱巨的任務(wù)。但是蘋果爸爸為開發(fā)者提供了兩套更好的解決方案:NSOperation裤园,Grand Central Dispatch (GCD) Reference撤师,GCD的方式具體的本文暫不討論。
使用NSOperationQueue 和 NSOperation的方式是蘋果基于GCD再一次封裝的一層拧揽,比GCD更加的靈活剃盾,而且是一種面向?qū)ο笤O(shè)計,更加適合開發(fā)人員淤袜。雖然相對于GCD會犧牲一些性能痒谴,但是我們可以對線程進(jìn)行更多的操作,比如暫停铡羡,取消积蔚,添加Operation間的依賴。但是GCD如果暫停和取消線程操作則十分的麻煩烦周。
Operation Queues vs. Grand Central Dispatch (GCD)
簡單來說库倘,GCD 是蘋果基于 C 語言開發(fā)的,一個用于多核編程的解決方案论矾,主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對稱多處理系統(tǒng)。而 Operation Queues 則是一個建立在 GCD 的基礎(chǔ)之上的杆勇,面向?qū)ο蟮慕鉀Q方案贪壳。它使用起來比 GCD 更加靈活,功能也更加強(qiáng)大蚜退。下面簡單地介紹了 Operation Queues 和 GCD 各自的使用場景:
Operation Queues :相對 GCD 來說闰靴,使用 Operation Queues 會增加一點點額外的開銷,但是我們卻換來了非常強(qiáng)大的靈活性和功能钻注,我們可以給 operation 之間添加依賴關(guān)系蚂且、取消一個正在執(zhí)行的 operation 、暫停和恢復(fù) operation queue 等幅恋;
GCD :則是一種更輕量級的杏死,以 FIFO 的順序執(zhí)行并發(fā)任務(wù)的方式,使用 GCD 時我們并不關(guān)心任務(wù)的 調(diào)度情況捆交,而讓系統(tǒng)幫我們自動處理淑翼。但是 GCD 的短板也是非常明顯的,比如我們想要給任務(wù)之間添加依賴關(guān)系品追、取消或者暫停一個正在執(zhí)行的任務(wù)時就會變得非常棘手玄括。
上引用自Operation Queues vs. Grand Central Dispatch (GCD)
關(guān)于Operation對象
NSOperation
對象是一個抽象類,是不能直接創(chuàng)建對象的肉瓦。但是它有兩個子類——NSBlockOperation
遭京,NSInvocationOperation
.通常情況下我們都可以直接使用這兩個子類胃惜,創(chuàng)建可以并發(fā)的任務(wù)。
我們查看關(guān)于NSOperation.h的頭文件哪雕,可以發(fā)現(xiàn)任意的operation對象都可以自行開始任務(wù)(start)船殉,取消任務(wù)(cancle),以及添加依賴(addDependency:)和移除依賴(removeDependency:).關(guān)于依賴热监,有一種很好的一種開發(fā)思路捺弦。在operation對象中有很多屬性,可以用于檢測當(dāng)前任務(wù)的狀態(tài)孝扛,如isCancelled
:是否已經(jīng)取消列吼,isFinished
:是否已經(jīng)完成了任務(wù)。
- 創(chuàng)建NSBlockOperation
以下使用到的代碼片段取自我的LSOperationAndOperationQueueDemo
NSBlockOperation
顧名思義苦始,是是用block來創(chuàng)建任務(wù)寞钥,主要有兩種方式創(chuàng)建,一種是是用類方法陌选,一種是創(chuàng)建operation對象理郑,再添加任務(wù)。上代碼:以下代碼包括了兩種block創(chuàng)建任務(wù)的方式咨油。以及已經(jīng)有任務(wù)的operation對象再添加任務(wù)您炉。及直接添加任務(wù)到queue中。
@implementation LSBlockOperation
+ (LSBlockOperation *)lsBlockOperation {
return [[LSBlockOperation alloc] init];
}
- (void)operatingLSBlockOperation {
NSBlockOperation *blockOpt1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"-------- blockOpt1, mainThread:%@, currentThread:%@", [NSThread mainThread], [NSThread currentThread]);
}];
/// 繼續(xù)添加執(zhí)行的block
[blockOpt1 addExecutionBlock:^{
NSLog(@"-------- blockOpt1 addExecutionBlock1 mainThread:%@, currentThread:%@", [NSThread mainThread], [NSThread currentThread]);
}];
[blockOpt1 addExecutionBlock:^{
NSLog(@"-------- blockOpt1 addExecutionBlock2 mainThread:%@, currentThread:%@", [NSThread mainThread], [NSThread currentThread]);
}];
NSBlockOperation *blockOpt2 = [[NSBlockOperation alloc] init];
[blockOpt2 addExecutionBlock:^{
NSLog(@"-------- blockOpt2 mainThread:%@, currentThread:%@", [NSThread mainThread], [NSThread currentThread]);
}];
NSBlockOperation *blockOpt3 = [[NSBlockOperation alloc] init];
[blockOpt3 addExecutionBlock:^{
NSLog(@"-------- blockOpt3 mainThread:%@, currentThread:%@", [NSThread mainThread], [NSThread currentThread]);
}];
NSBlockOperation *blockOpt4 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"-------- blockOpt4 mainThread:%@, currentThread:%@", [NSThread mainThread], [NSThread currentThread]);
}];
// 添加執(zhí)行優(yōu)先級 - 并不能保證執(zhí)行順序
// blockOpt2.queuePriority = NSOperationQueuePriorityVeryHigh;
// blockOpt4.queuePriority = NSOperationQueuePriorityHigh;
/// 可以設(shè)置Operation之間的依賴關(guān)系 - 執(zhí)行順序3 2 1 4
[blockOpt2 addDependency:blockOpt3];
[blockOpt1 addDependency:blockOpt2];
[blockOpt4 addDependency:blockOpt1];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:blockOpt1];
[queue addOperation:blockOpt2];
[queue addOperation:blockOpt3];
[queue addOperation:blockOpt4];
[queue addOperationWithBlock:^{
NSLog(@"-------- queue addOperationWithBlock1 mainThread:%@, currentThread:%@", [NSThread mainThread], [NSThread currentThread]);
}];
[queue addOperationWithBlock:^{
NSLog(@"-------- queue addOperationWithBlock2 mainThread:%@, currentThread:%@", [NSThread mainThread], [NSThread currentThread]);
}];
}
- 創(chuàng)建NSInvocationOperation
NSInvocationOperation
是另一種可創(chuàng)建的operation對象的類役电。但是在Swift中已經(jīng)被去掉了赚爵。NSInvocationOperation
是一種可以非常靈活的創(chuàng)建任務(wù)的方式,主要是其中包含了一個target
和selector
法瑟。假設(shè)我們現(xiàn)在有一個任務(wù)冀膝,已經(jīng)在其它的類中寫好了,為了避免代碼的重復(fù)霎挟,我們可以將當(dāng)前的target
指向為那個類對象窝剖,方法選擇器指定為那個方法即可,如果有參數(shù)酥夭,可以在NSInvocationOperation
創(chuàng)建中指定對應(yīng)的Object(參數(shù)).
具體的可以看如下代碼:LSOperationAndOperationQueueDemo
@implementation LSInvocationOperation
+ (LSInvocationOperation *)lsInvocationOperation {
return [[LSInvocationOperation alloc] init];
}
- (void)operationInvocationOperation {
NSInvocationOperation *invoOpt1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invoOperated1) object:self];
NSInvocationOperation *invoOpt2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invoOperated2) object:self];
// invocated other obj method
/// 可以執(zhí)行其它類中方法赐纱,并且可以帶參數(shù)
NSInvocationOperation *invoOpt4 = [[NSInvocationOperation alloc] initWithTarget:[[Person alloc] init] selector:@selector(running:) object:@"linsir"];
// 設(shè)置優(yōu)先級 - 并不能保證按指定順序執(zhí)行
// invoOpt1.queuePriority = NSOperationQueuePriorityVeryLow;
// invoOpt4.queuePriority = NSOperationQueuePriorityVeryLow;
// invoOpt2.queuePriority = NSOperationQueuePriorityHigh;
// 設(shè)置依賴 - 線性執(zhí)行
[invoOpt1 addDependency:invoOpt2];
[invoOpt2 addDependency:invoOpt4];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:invoOpt1];
[queue addOperation:invoOpt2];
[queue addOperation:invoOpt4];
}
- (void)invoOperated1 {
NSLog(@"--------- invoOperated1, mainThread:%@, currentThread:%@", [NSThread mainThread],[NSThread currentThread]);
}
- (void)invoOperated2 {
NSLog(@"--------- invoOperated2, mainThread:%@, currentThread:%@", [NSThread mainThread],[NSThread currentThread]);
}
@end
自定義Operation對象
上文介紹了兩種系統(tǒng)定義的NSOperation,通常情況下熬北,我們可以直接使用千所,已經(jīng)可以滿足了大部分的需求。但是當(dāng)系統(tǒng)的不能滿足時候蒜埋,我們就需要自定義我們自己的Operation對象淫痰。Operation對象可以分為并發(fā)的和非并發(fā)的兩類。從實現(xiàn)角度上而言整份,非并發(fā)的更容易實現(xiàn)的多待错。因為非并發(fā)的Operation對象中的很多屬性籽孙,它的父類已經(jīng)做好了管理,我們只需要直接使用就可以了火俄。(通常情況下犯建,實現(xiàn)多線程是由NSOperationQueue對象管理的,而不是NSOperation對象)實現(xiàn)自定義的NSOperation對象瓜客,最少需要重寫兩個方法适瓦,一個是初始化init方法(傳值),一個是mian方法(主要的邏輯實現(xiàn))谱仪。
-
自定義的非并發(fā)NSOperation-不實現(xiàn)取消操作
代碼片段取自LSOperationAndOperationQueueDemo
@interface LSNonConcurrentOperation () @property (nonatomic, strong)id data; @end /** 自定義一個非并發(fā)的Operation玻熙,最少需要實現(xiàn)兩個方法,一個初始化的init方法疯攒,另一個是mian方法嗦随,即主方法,邏輯的主要執(zhí)行體敬尺。 */ @implementation LSNonConcurrentOperation - (id)initWithData:(id)data { self = [self init]; if (self) { self.data = data; } return self; } // 該主方法不支持Operation的取消操作 - (void)main { @try { NSLog(@"-------- LSNonConcurrentOperation - data:%@, mainThread:%@, currentThread:%@", self.data, [NSThread mainThread], [NSThread currentThread]); sleep(2); NSLog(@"-------- finish executed %@", NSStringFromSelector(_cmd)); } @catch (NSException *exception) { NSLog(@"------- LSNonConcurrentOperation exception - %@", exception); } @finally { } }
-
自定義的非并發(fā)NSOperation-實現(xiàn)取消操作
- (void)main { // 執(zhí)行之前枚尼,檢查是否取消Operation if (self.isCancelled) return; @try { NSLog(@"-------- LSNonConcurrentOperation - data:%@, mainThread:%@, currentThread:%@", self.data, [NSThread mainThread], [NSThread currentThread]); // 循環(huán)去檢測執(zhí)行邏輯過程中是否取消當(dāng)前正在執(zhí)行的Operation for (NSInteger i = 0; i < 10000; i++) { NSLog(@"run loop -- %@", @(i + 1)); if (self.isCancelled) return; sleep(1); } NSLog(@"-------- finish executed %@", NSStringFromSelector(_cmd)); } @catch (NSException *exception) { NSLog(@"------- LSNonConcurrentOperation exception - %@", exception); } @finally { } }
由上可以知道,取消一個任務(wù)的執(zhí)行砂吞,其實并不是立即就會取消署恍,而是會在一個runloop中不斷的去檢查,判斷isCancle的值蜻直,直到為yes時候盯质,則取消了操作。所以袭蝗,設(shè)置Operation為cancle的時候,至少需要一個runloop的時間才會結(jié)束操作般婆。
定制Operation對象的執(zhí)行行為
- 修改Operation在隊列中的優(yōu)先級
NSOperation
對象在Queue中可以設(shè)置執(zhí)行任務(wù)的優(yōu)先級到腥。我們可以通過設(shè)置operation對象的setQueuePriority:
方法,改變?nèi)蝿?wù)在隊列中的執(zhí)行優(yōu)先級蔚袍。但是真正決定一個operation對象能否執(zhí)行的是isReady
乡范,假設(shè)一個operation對象的在隊列執(zhí)行的優(yōu)先級很高,另一個很低啤咽,但是高的operation對象的isReady
是NO晋辆,也只會執(zhí)行優(yōu)先級低的operation任務(wù)。另一個影響任務(wù)在隊列中執(zhí)行順序的是依賴(下文會講到)宇整,假設(shè)operation A依賴于operation B瓶佳,所以一定先執(zhí)行operation B,再執(zhí)行operation A.
- 修改Operation執(zhí)行任務(wù)線程的優(yōu)先級
從iOS4.0開始,我們可以設(shè)置operation中任務(wù)執(zhí)行的線程優(yōu)先級鳞青。從iOS4.0到iOS8.0霸饲,operation對象可以通過方法setThreadPriority:
为朋,這里的參數(shù)是一個double類型,范圍是0.0到1.0厚脉,設(shè)置越高习寸,理論上講,線程執(zhí)行的可能性就越高傻工。但是從iOS8.0之后霞溪,這個方法已經(jīng)被廢棄了,使用setQualityOfService:
代替中捆,這里參數(shù)是一個預(yù)設(shè)的枚舉值鸯匹。
- 設(shè)置Completion Block
同上,從iOS4.0開始轨香,可以給每個operation對象設(shè)置一個主任務(wù)完成之后的完成回調(diào)setCompletionBlock:
忽你。所設(shè)置的block執(zhí)行是在檢測到operation的isFinished
為YES后執(zhí)行的。值得注意的是:我們并不能保證block所在的線程一定在主線程臂容,所以當(dāng)我們需要對主線程上做一些操作的時候科雳,我們應(yīng)該切換線程到主線程中,如需在其他線程執(zhí)行的某些操作脓杉,亦需要切換線程糟秘。
Therefore, you should not use this block to do any work that requires a very specific execution context. Instead, you should shunt that work to your application’s main thread or to the specific thread that is capable of doing it.
執(zhí)行Operation對象
對于執(zhí)行一個Operation對象,一般的做法是將operation對象添加到一個隊列中去球散,之后隊列會根據(jù)當(dāng)前系統(tǒng)的狀態(tài)尿赚,以及內(nèi)核的狀態(tài),自行的去執(zhí)行operation中的任務(wù)蕉堰。
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:opt];
還有一種做法是凌净,我們可以手動的執(zhí)行一個operation對象,直接調(diào)用operation的start
方法
[opt start];
- 添加Operation到Operation Queue中
將operation對象添加到queue中非常簡單
首先創(chuàng)建一個隊列:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
添加到隊列的方法如下:
- (void)addOperation:(NSOperation *)op;
- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);
- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);
第一個:添加意境存在的operation對象
第二個:添加一組operation對象
第三個:直接添加一個block到隊列中屋讶,無需創(chuàng)建operation對象
- 手動執(zhí)行Operation
一般情況下冰寻,我們不需要手動的去執(zhí)行一個operation對象,但如果需要皿渗,亦可斩芭,調(diào)用start
方法。
[opt start];
- 取消Operation
當(dāng)我們將一個operation對象添加到隊列中之后乐疆,operation就已經(jīng)被隊列所擁有划乖。我們可以在某個需要的時候調(diào)用operation對象的cancle
方法,將operation出列挤土。并且此時operation的isFinished
也會為YES琴庵,所以此時依賴于它的operation就回繼續(xù)得到執(zhí)行。當(dāng)然,我們可以直接調(diào)用隊列的cancelAllOperations
方法细卧,取消了隊列中所有的operation執(zhí)行尉桩。
- 等待Operation執(zhí)行完成
等待一個Operation對象的執(zhí)行完成,可以使用waitUntilFinished
方法贪庙。但是應(yīng)該注意到蜘犁,等待一個任務(wù)執(zhí)行完,會阻塞當(dāng)前線程止邮。所以我們絕不應(yīng)該在主線程中做該操作这橙,那樣會帶來非常差的體驗。所以該操作應(yīng)該使用輔助線程中导披。
我們也可以調(diào)用NSOperationQueue
對象的waitUntilAllOperationsAreFinished
方法屈扎,知道所有的任務(wù)都執(zhí)行完成。
- 暫停和恢復(fù)Operation Queue
通過設(shè)置隊列的setSuspended
,我們可以暫停一個隊列中還沒有開始執(zhí)行的operation對象撩匕,對于已經(jīng)開始的執(zhí)行的任務(wù)鹰晨,將繼續(xù)執(zhí)行。并且止毕,已經(jīng)暫停了隊列模蜡,仍然可以繼續(xù)添加operation對象,但是不會執(zhí)行扁凛,只能等到從暫停(掛起)狀態(tài)切換到非暫停狀態(tài)忍疾。即設(shè)置setSuspended
為NO。對于單個的operation谨朝,是沒有暫停的概念的卤妒。
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.
添加Operation Queue中Operation對象之間的依賴
在NSOperationQueue
中,如果沒有經(jīng)過對operation添加依賴字币,都是使用并發(fā)處理的则披。但是在某些情況下,我們對任務(wù)的執(zhí)行是有非常嚴(yán)格的規(guī)定的洗出。即需要串行執(zhí)行士复,此時,我們就需要對operation對象間進(jìn)行添加依賴處理共苛。
- (void)addDependency:(NSOperation *)op;
- (void)removeDependency:(NSOperation *)op;
第一個:添加依賴
第二個:移除依賴
依賴判没,是一種非常好用功能蜓萄,在我們做項目(生活中)的時候隅茎,很多時候都一種依賴的概念。比如嫉沽,用戶需要上傳一張照片到自己的空間辟犀,但是此時必須檢測該用戶是否已經(jīng)登錄。以前我們可能將兩個邏輯寫在一起绸硕,但是現(xiàn)在可以將成寫成兩個不同的operation堂竟,并設(shè)置它們的依賴魂毁。這樣的好處非常可見的:
第一點:它可以幫我們解藕出嘹,不同的邏輯分在不一樣的對象中席楚。
第二點:某些常用的邏輯會經(jīng)常用到,以后不需要一次次的重復(fù)税稼,可讀性增強(qiáng)烦秩,以后需要的時候直接調(diào)用,設(shè)置其依賴即可郎仆。比如檢測是否登錄
總結(jié)
多線程執(zhí)行任務(wù)看似十分的復(fù)雜只祠,但是如果將復(fù)雜的任務(wù)交給NSOperation
and NSOperationQueue
,就可以簡化它的難度扰肌,并且它似乎可以比我們自己處理的更好抛寝。
文中使用的demo - LSOperationAndOperationQueueDemo
感謝
以下文章給我?guī)矸浅4蟮膸椭?/p>
還在疑惑并發(fā)和并行?
進(jìn)程與線程的一個簡單解釋
iOS 并發(fā)編程之 Operation Queues
NSOperationQueue - 文檔