標籤:
本文講建立分線程的方法。
- 第一種:直接調用NSObject的方法
- 第二種:利用NSThread建立新線程
- 第三種:建立NSOperation,並加入到隊列NSOperationQueue中
- 第四種:GCD (grand central dispatch)線程最佳化技術
第一種:直接調用NSObject的方法
所有的類都是NSObject的子類,因此都繼承了這些方法:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait; // equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait); // equivalent to the first method with kCFRunLoopCommonModes
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
最後一個方法是在後台執行的,不要用這個方法去做修改ui的動作。
第二種:用NSThread建立新線程1、利用NSThread的初始化方法建立新線程
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument
建立後需要手動開啟線程。
2、用NSThread類的類方法建立新線程
- (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument
建立後線程自動開啟。不論用哪種方法建立的線程,要保持安全執行緒要用到NSLock。這個NSLock,鎖的是在其lock方法到unlock方法之間的代碼,鎖定之後,這段代碼同一時間只會被一個線程執行。
第三種:建立NSOperation,並加入到隊列NSOperationQueue中
NSOperationQueue會建立一定數量的線程來執行加入到其中的NSOperation,可以設定本隊列可同時執行的最大線程數maxConcurrentOperationCount,預設的話,最大線程數為NSOperationQueueDefaultMaxConcurrentOperationCount,這個數值會根據系統內容自動適應。
加入到隊列的NSOperation的並存執行。
如果需要指定順序,可以通過添加依賴。一個NSOperation對象,只有當其依賴的NSOperation對象執行完,才會執行。
一個app可以有多個隊列,各個隊列分別管理各自的NSOperation對象。
NSOperation有許多子類,比如NSInnovationOperation、NSBlockOperation,這些子類的區別僅僅在於與動作綁定的方式不一樣,隊列會被同等對待它們。
第四種:GCD (grand central dispatch)
GCD是一個可以發揮多核處理器效能的任務管理技術。
GCD與NSOperationQueue比較像的地方是,它也有隊列,類型為dispatch_queue_t。和NSOperationQueue一樣,隊列管理著許多待執行的任務。
不同的是,GCD分為並行隊列和串列隊列。串列隊列會將加入其中的任務全部放到一個線程中執行,而並行隊列執行任務的思想和NSOperationQueue類似,會用多線程來執行任務。
1、建立隊列:
(1)直接擷取
主線程的運行也是有一個隊列在進行管理,這個隊列是main()方法執行的時候就已經建立了的,可以直接利用這個隊列,然後往裡面新增工作。要注意這個隊列是串列隊列。
擷取主線程所在隊列:
dispatch_get_main_queue();
(2)自己建立新隊列:
dispatch_queue_create(const char *label,dispatch_queue_attr_t attr);
第一個參數是隊列名字,第二個參數決定這個隊列是串列隊列還是並行隊列,其值為常量:DISPATCH_QUEUE_SERIAL或者DISPATCH_QUEUE_CONCURRENT。
2、往隊列中新增工作
任務很多時候以block的形式添加到隊列。
(1)添加block任務的兩個基本方法
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
兩個方法的第一個參數是隊列,第二個參數是要加入的block。
區別在於,第一個方法是非同步方法呼叫,會將block加入到隊列之後就會馬上返回;第二個方法是同步方法,會等到加入的block執行完以後才會返回。
因此要注意,由於主線程所在隊列為串列隊列,如果使用同步方法往裡面加入的block,由於block搶不過主線程而一直得不到執行,但是不執行又不返回,最終導致應用程式卡死。也就是說,新增工作到串列隊列最好使用非同步方法呼叫。
儘管串列隊列所管理的任務是在一個線程中串列執行的,但串列隊列和其他隊列之間則是並存執行的。
(2)添加延時block任務的方法:
dispatch_after(dispatch_time_t when,dispatch_queue_t queue,dispatch_block_t block);
不管加入的是串列隊列還是並行隊列,都會延時執行。
(3)添加block任務到隊列的時候標記分組
建立分組
dispatch_group_t queueGroup= dispatch_group_create();
添加block到隊列且標記分組
dispatch_group_async(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
標記了分組的任務和沒有標記分組的任務對於隊列來說是沒有區別的,標記分組影響的只是任務相對於其他任務的執行次序。
(4)一些添加block任務的特殊方法。
方法一:
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
用這個方法添加的block會成為路障:在它之前加入到隊列的任務都執行完,才輪到它執行,等它執行完以後,之後的任務才可以執行。這個方法只對並行隊列生效。
方法二:
dispatch_group_notify(dispatch_group_t group,dispatch_queue_t queue,dispatch_block_t block);
執行這個方法時,block仍未排入佇列,只是先登記了,要等執行這個方法之前已標記了group的任務都完成了,才會真正排入佇列。
驗證一下:
與添加block到隊列的基本方法綜合起來驗證這些特殊方法的效果():
代碼中建立了一個並行隊列,建立了一個分組。
然後往這個隊列中加入了若干block,按方法的執行順序命名為任務1~任務7。
任務1和任務4標記了分組,其他沒有標記,任務3為路障,任務6先要等標記了相同分組的任務完成後才會排入佇列。任務之間原則上並存執行。
運行結果:
從運行結果可以看到,
任務1和任務2在任務3(路障)之前加入到隊列,因此都在任務3之前執行完畢。
任務4、5、7是並存執行的。
任務6等標記了相同分組的任務4完成後才排入佇列。
iOS 多線程的管理