在iOS開發中,談到多執行緒,大家第一時間想到的一定是GCD。GCD固然是一套強大的多執行緒解決方案,能夠解決絕大多數的多執行緒問題,但是他易於上手難於精通且到處是坑的特點也註定了想熟練使用它有一定的難度。而且很多人嘴上天天掛著GCD,實際上對它的實際應用也不甚瞭解。
再者說,在現在的主流開發模式下,能用到多執行緒的絕大多數就是網路資料請求和網路圖片載入,這兩點上AFNetwork+SDWebImage已經能滿足幾乎所有的需求。而剩下的一小部分,簡單好用的NSOperation無疑是比GCD更有優勢的。
因此,如果你還是堅持『GCD大法好』,那看到這裡就不必再看了。如果你想試一試更簡單的方法,那就隨我來吧。
什麼是NSOperation?
和GCD一樣,NSOperation也是蘋果提供給我們的一套多執行緒解決方案。實際上它也是基於GCD開發的,但是比GCD擁有更強的可控性和程式碼可讀性。
NSOperation是一個抽象基類,基本沒有什麼實際使用價值。我們使用最多的是系統封裝好的NSInvocationOperation
和NSBlockOperation
。
不過NSOperation一些通用的方法你要知道
1 2 3 4 5 6 7 8 9 |
NSOperation * operation = [[NSOperation alloc]init]; //開始執行 [operation start]; //取消執行 [operation cancel]; //執行結束後呼叫的Block [operation setCompletionBlock:^{ NSLog(@"執行結束"); }]; |
使用NSInvocationOperation
NSInvocationOperation的使用方式和給Button新增事件比較相似,需要一個物件和一個Selector。使用方法非常簡單。
我們先來寫一個方法
1 2 3 4 |
- (void)testNSOperation { NSLog(@"我在第%@個執行緒",[NSThread currentThread]); } |
然後呼叫它
1 2 3 4 |
//建立 NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation) object:nil]; //執行 [invo start]; |
得到這樣的執行結果
我們可以看到NSInvocationOperation其實是同步執行的,因此單獨使用的話,這個東西也沒有什麼卵用,它需要配合我們後面介紹的NSOperationQueue去使用才能實現多執行緒呼叫,所以這裡我們只需要記住有這麼一個東西就行了。
使用NSBlockOperation
- 終於到了我們今天的第一個重點
NSBlockOperation也是NSOperation的子類,支援併發的實行一個或多個block,使用起來簡單又方便
執行以下程式碼
12345678910111213141516171819NSBlockOperation * blockOperation = [[NSBlockOperationblockOperationWithBlock:^{NSLog(@"1在第%@個執行緒",[NSThread currentThread]);}];[blockOperation addExecutionBlock:^{NSLog(@"2在第%@個執行緒",[NSThread currentThread]);}];[blockOperation addExecutionBlock:^{NSLog(@"3在第%@個執行緒",[NSThread currentThread]);}];[blockOperation addExecutionBlock:^{NSLog(@"4在第%@個執行緒",[NSThread currentThread]);}];[blockOperation addExecutionBlock:^{NSLog(@"5在第%@個執行緒",[NSThread currentThread]);}];[blockOperation addExecutionBlock:^{NSLog(@"6在第%@個執行緒",[NSThread currentThread]);}];
這裡我們多執行兩次並比較結果
- 通過三次不同結果的比較,我們可以看到,NSBlockOperation確實實現了多執行緒。但是我們可以看到,它並非是將所有的block都放到放到了子執行緒中。通過上面的列印記錄我們可以發現,它會優先將block放到主執行緒中執行,若主執行緒已有待執行的程式碼,就開闢新的執行緒,但最大併發數為4(包括主執行緒在內)。如果block數量大於了4,那麼剩下的Block就會等待某個執行緒空閒下來之後被分配到該執行緒,且依然是優先分配到主執行緒。
- 另外,同一個block中的程式碼是同步執行的
為了證明以上猜想,我們為它增加更多block,並給每條block新增兩行程式碼。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1在第%@個執行緒",[NSThread currentThread]); NSLog(@"1haha"); }]; [blockOperation addExecutionBlock:^{ NSLog(@"2在第%@個執行緒",[NSThread currentThread]); NSLog(@"2haha"); }]; [blockOperation addExecutionBlock:^{ NSLog(@"3在第%@個執行緒",[NSThread currentThread]); NSLog(@"3haha"); }]; [blockOperation addExecutionBlock:^{ NSLog(@"4在第%@個執行緒",[NSThread currentThread]); NSLog(@"4haha"); }]; [blockOperation addExecutionBlock:^{ NSLog(@"5在第%@個執行緒",[NSThread currentThread]); NSLog(@"5haha"); }]; [blockOperation addExecutionBlock:^{ NSLog(@"6在第%@個執行緒",[NSThread currentThread]); NSLog(@"6haha"); }]; [blockOperation addExecutionBlock:^{ NSLog(@"7在第%@個執行緒",[NSThread currentThread]); NSLog(@"7haha"); }]; [blockOperation addExecutionBlock:^{ NSLog(@"8在第%@個執行緒",[NSThread currentThread]); NSLog(@"8haha"); }]; [blockOperation addExecutionBlock:^{ NSLog(@"9在第%@個執行緒",[NSThread currentThread]); NSLog(@"9haha"); }]; [blockOperation addExecutionBlock:^{ NSLog(@"10在第%@個執行緒",[NSThread currentThread]); NSLog(@"10haha"); }]; [blockOperation start]; |
然後我們看一下執行結果
]
- 我們可以看到,最大併發數為4,使用同一個執行緒的block一定是等待前一個block的程式碼全部執行結束後才執行,且同步執行。
關於最大併發數
在剛才的結果中我們看到最大併發數為4,但這個值並不是一個固定值。4是我在模擬器上執行的結果,而如果我使用真機來跑的話,最大併發數始終為2。因此,具體的最大併發數和執行環境也是有關係的。我們不必糾結於這個數字
所以NSBlockOperation也不是一個理想的多執行緒解決方案,儘管我們可以在第一個block中建立UI,在其他Block做資料處理等操作,但還是感覺哪裡不舒服。
彆著急,我們繼續往下看
自定義NSOperation
是的,你沒看錯,NSOperation是可以自定義的。如果NSInvocationOperation
和NSBlockOperation
無法滿足你的需求,你可以選擇自定義一個NSOperation。
經過上面的分析,我們發現,系統提供的兩種NSOperation是一定滿足不了我們的需求的。
那我們是不是需要自定義一個NSOperation呢?
答案是,不需要。
自定義NSOperation並不難,但是依然要寫不少程式碼,這違背了我們簡單實現多執行緒的初衷。況且,接下來我會介紹我們今天真正的主角–NSOperationQueue。所以,我打算直接跳過這一個環節。
如果確實有同學需要的話,可以私信我。。。 如果很多人需要的話。。 我會額外寫一篇。。。
(讀者:你TM不講還這麼多廢話(╯‵□′)╯︵┻━┻)
NSOPerationQueue
簡單使用
終於輪到我們今天的主角了。
顧名思義,NSOperationQueue就是執行NSOperation的佇列,我們可以將一個或多個NSOperation物件放到佇列中去執行。
比如我們上面介紹過的NSInvocationOperation,我們來將它放到佇列中來
1 2 3 4 5 |
//依然呼叫上面的那個方法 NSInvocationOperation * invo = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation) object:nil]; NSOperationQueue * queue = [[NSOperationQueue alloc]init]; [queue addOperation:invo]; |
看一下執行結果
現在它已經被放到子執行緒中執行了
我們把剛才寫的NSBlockOperation也加到這個Queue中來
1 2 3 |
...原來的程式碼 //[blockOperation start]; [queue addOperation:blockOperation]; |
然後我們再來看執行情況
我們看到,NSInvocationOperation 和 NSBlockOperation是非同步執行的,NSBlockOperation中的每一個Block也是非同步執行且都在子執行緒中執行,每一個Block內部也依然是同步執行。
是不是簡單好用又強大?
放入佇列中的NSOperation物件不需要呼叫
start
方法,NSOPerationQueue會在『合適』的時機去自動呼叫
更簡單的使用方式
除了上述的將NSOperation新增到佇列中的使用方法外,NSOperationQueue提供了一個更加簡單的方法,只需以下兩行程式碼就能實現多執行緒呼叫
1 2 3 4 |
NSOperationQueue * queue = [[NSOperationQueue alloc]init]; [queue addOperationWithBlock:^{ //這裡是你想做的操作 }]; |
你可以同時新增一個或這個多個Block來實現你的操作
怎麼樣,是不是簡單的要死?
(原來這篇文章只需要看這兩句就行了是嘛?)
新增依賴關係
如果NSOperationQueue僅能做到這些,那我也不必大費周章了。
NSOperationQueue最吸引人的無疑是它的新增依賴的功能。
舉個例子,假如A依賴於B,那麼在B執行結束之前,A將永遠不會執行
示例程式碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
{ NSInvocationOperation * op1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation1) object:nil]; NSInvocationOperation * op2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(testNSInvocationOperation2) object:nil]; NSOperationQueue * queue = [[NSOperationQueue alloc]init]; [op2 addDependency:op1]; [queue addOperation:op1]; [queue addOperation:op2]; } - (void)testNSInvocationOperation1 { NSLog(@"我是op1 我在第%@個執行緒",[NSThread currentThread]); } - (void)testNSInvocationOperation2 { NSLog(@"我是op2 我在第%@個執行緒",[NSThread currentThread]); } |
然後無論你執行多少次,得到的一定是這樣的結果
這就是依賴關係的好處,op2必定會在op1之後執行,這樣會大大方便我們的流程控制。
使用依賴關係有三點需要注意
1.不要建立迴圈依賴,會造成死鎖,原因同迴圈引用
2.使用依賴建議只使用NSInvocationOperation,NSInvocationOperation和NSBlockOperation混用會導致依賴關係無法正常實現。
3.依賴關係不光在同佇列中生效,不同佇列的NSOperation物件之前設定的依賴關係一樣會生效
2016年03月29日11:16:00更新
之前放的程式碼有一點小小的問題 新增依賴的程式碼最好放到新增佇列之前
前面說過,NSOperationQueue會在『合適』的時間自動去執行任務,因此你無法確定它到底何時執行,有可能前一秒新增的任務,在你這一秒準備新增依賴的時候就已經執行完了,就會出現依賴無效的假象。程式碼已更正,謝謝評論區各位提醒
設定優先順序
每一個NSOperation的物件都一個queuePriority
屬性,表示佇列優先順序。它是一個列舉值,有這麼幾個等級可選
大家可以去設定試試,不過它並不總是起作用,目前我還沒有找到原因。所以還是建議用依賴關係來控制流程。
如果有小夥伴知道怎麼讓優先順序始終生效的辦法,請告知我。。。
其他操作及注意事項
NSOperationQueue提供暫停和取消兩種操作。
設定暫停只需要設定queue的suspended
屬性為YES
或NO
即可
取消你可以選擇呼叫某個NSOperation的cancle
方法,也可以呼叫Queue的cancelAllOperations
方法來取消全部執行緒
這裡需要強調的是,所謂的暫停和取消並不會立即暫停或取消當前操作,而是不在呼叫新的NSOperation。
改變queue的maxConcurrentOperationCount可以設定最大併發數。
這裡依然有兩點需要注意
1.最大併發數是有上限的,即使你設定為100,它也不會超過其上限,而這個上限的數目也是由具體執行環境決定的
2.設定最大併發數一定要在NSOperationQueue初始化後立即設定,因為上面說過,被放到佇列中的NSOperation物件是由佇列自己決定何時執行的,有可能你這邊一新增立馬就被執行。因此要想讓設定生效一定要在初始化後立即設定
結束語
到這裡,NSOperation的知識我們已經介紹完畢,如果你嘗試用一兩次的話,你一定會愛上他。
作者水平有限,不正確的地方請指出