1. 并行和并發(fā)
簡單來說喉恋,若說兩個任務A和B并發(fā)執(zhí)行,則表示任務A和任務B在同一時間段里被執(zhí)行(更多的可能是二者交替執(zhí)行)逊躁;若說任務A和B并行執(zhí)行谢翎,則表示任務A和任務B在同時被執(zhí)行(這要求計算機有多個運算器);
一句話:并行要求并發(fā)走趋,但并發(fā)并不能保證并行衅金。
2. Dispatch Queues
Dispatch Queue
是一個任務執(zhí)行隊列,可以讓你異步或同步地執(zhí)行多個Block或函數(shù)簿煌。Dispatch Queue是FIFO的氮唯,即先入隊的任務總會先執(zhí)行。目前有三種類型的Dispath Queue:
- 串行隊列(Serial dispatch queue)
- 并發(fā)隊列(Concurrent dispatch queue)
- 主隊列(Main dispatch queue)
2.1 Serial Dispatch Queue 串行隊列
serial dispatch queue中的block按照先進先出(FIFO)的順序去執(zhí)行姨伟,實際上為單線程執(zhí)行惩琉。即每次從queue中取出一個task進行處理;用戶可以根據(jù)需要創(chuàng)建任意多的serial dispatch queue夺荒,serial dispatch queue彼此之間是并發(fā)的瞒渠;
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MySerialQueue", DISPATCH_QUEUE_SERIAL);
2.2 Concurrent Dispatch Queue 并行隊列
Concurrent Dispatch Queue
相對于Serial Dispatch Queue,Concurrent Dispatch Queue一次性并發(fā)執(zhí)行一個或者多個task技扼;和Serial Dispatch Queue不同伍玖,系統(tǒng)提供了四個global concurrent queue,使用dispatch_get_global_queue函數(shù)就可以獲取這些global concurrent queue剿吻;
和Serial Dispatch Queue一樣窍箍,用戶也可以根據(jù)需要自己定義concurrent queue;創(chuàng)建concurrent dispatch queue也使用dispatch_queue_create方法丽旅,所不同的是需要指定其第二個參數(shù)為DISPATCH_QUEUE_CONCURRENT即可:
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
2.3 Main Dispatch Queue 主隊列
The main dispatch queue is a globally available serial queue that executes tasks on the application’s main thread.
根據(jù)我的理解椰棘,application的主要任務(譬如UI管理之類的)都在main dispatch queue中完成;根據(jù)文檔的描述魔招,main dispatch queue中的task都在一個thread中運行晰搀,即application’s main thread(thread 1)。
P.S: 所以办斑,如果想要更新UI外恕,則必須在main dispatch queue中處理,獲取main dispatch queue也很容易乡翅,調(diào)用dispatch_get_main_queue()函數(shù)即可鳞疲。
注意
thread和dispatch queue之間沒有從屬關系
3. dispatch_sync和dispatch_async 同步和異步
一個結(jié)論:
dispatch_sync
派發(fā)的block的執(zhí)行線程和dispatch_sync
上下文線程是同一個線程;
dispatch_async
派發(fā)的block的執(zhí)行線程和dispatch_async
上下文線程不是同一個線程,主隊列 下異步任務還是在主隊列下執(zhí)行蠕蚜;
對于serial dispatch queue中的tasks尚洽,無論是同步派發(fā)還是異步派發(fā),其執(zhí)行順序都遵循FIFO;
4. 使用GCD來替代performSelector的原因:
4.1 performSelector
的局限性:
4.1.1 performSelector 會導致內(nèi)存泄漏問題
用performSelector:調(diào)用了一個方法靶累,編譯器并不知道將要調(diào)用的selector是什么腺毫,因此癣疟,也就不了解其方法簽名及返回值,甚至連是否有返回值都不清楚潮酒。而且睛挚,由于編譯器不知道方法名,所以就沒辦法用ARC的內(nèi)存管理規(guī)則來判定返回值是不是該釋放急黎。鑒于此扎狱,ARC采用了比較謹慎的做法,就是不添加釋放操作勃教。然而淤击,這么做可能導致內(nèi)存泄漏,因為方法在返回對象時已經(jīng)將其保留了故源。
4.1.2 performSelector 返回值只能是void或?qū)ο箢愋停╥d類型)
如果想返回整數(shù)或浮點數(shù)等scalar類型值污抬,那么就需要執(zhí)行一些復雜的轉(zhuǎn)換操作,而這種轉(zhuǎn)換操作很容易出錯心软。由于id類型表示指向任意Objective—C對象的指針壕吹,所以從技術上來講著蛙,只要返回的大小和指針所占大小相同就行删铃,也就是說,在32位架構(gòu)的計算機上踏堡,可以返回任意32位大小的類型猎唁;而在64位架構(gòu)的計算機上,則可以返回任意64位大小的類型顷蟆。除此之外诫隅,還可以返回NSNumber進行轉(zhuǎn)換…若返回的類型為C語言結(jié)構(gòu)體,則不可使用performSelector方法帐偎。
4.1.3 performSelector 提供的方法局限性大
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
具備延后功能的那些方法無法處理帶有兩個參數(shù)的選擇子逐纬。而能夠指定執(zhí)行線程的哪些方法,則與之類似削樊,所以也不是特別通用豁生。如果要用這些方法,就得把很多參數(shù)打包到字典中漫贞,然后在被調(diào)用的方法中將這些參數(shù)提取出來甸箱,這樣會增加開銷,同時也提高了產(chǎn)生bug的可能性迅脐。
5.GCD 中block的使用問題
5.1 GCD 會對添加的Block進行復制
Dispatchqueue對添加的Block會進行復制,在完成執(zhí)行后自動釋放芍殖。換 句話說,你不需要在添加 Block 到 Queue 時顯式地復制
5.2 GCD 中的autorelease pool
GCD dispatch queue 有自己的autorelease pool來管理內(nèi)存對象,但是不保證在什么時候會進行回收谴蔑,如果在block中創(chuàng)建了大量的對象豌骏,可以添加自己的autorelease pool來進行管理龟梦。
5.3 GCD 中在開新的線程執(zhí)行任務一定比較快嗎
如果對于工作量小的block切換線程的開銷,比直接在原來線程上執(zhí)行block的開銷要大窃躲,那么這樣的話变秦,會導致開新的線程反而沒有原來執(zhí)行的快
5.4 GCD 中的block會造成循環(huán)引用嗎
會,如果控制器持有block的話框舔,是會造成循環(huán)引用蹦玫,如果我們只持有queue是不會造成循環(huán)引用。
6. GCD 的暫停和繼續(xù)
- dispatch_suspend 會暫停一個隊列以阻止執(zhí)行block對象刘绣,調(diào)用 dispatch_suspend 會增加queue的引用計數(shù)
- dispatch_resume 會使得隊列恢復繼續(xù)執(zhí)行block對象樱溉,調(diào)用 dispatch_resume 會減少queue的引用計數(shù)
掛起和繼續(xù)是異步的,只在沒有執(zhí)行的block上生效纬凤,掛起一個block不會導致已經(jīng)開始執(zhí)行的block停止執(zhí)行福贞。
7. GCD中的信號量 Semaphore
7.1 信號量概念
停車場剩余4個車位,那么即使同時來了四輛車也能停的下停士。如果此時來了五輛車挖帘,那么就有一輛需要等待。
信號量的值就相當于剩余車位的數(shù)目恋技,dispatch_semaphore_wait函數(shù)就相當于來了一輛車拇舀,
dispatch_semaphore_signal,就相當于走了一輛車蜻底。停車位的剩余數(shù)目在初始化的時候就已經(jīng)指明了(dispatch_semaphore_create(long value))
調(diào)用一次dispatch_semaphore_signal骄崩,剩余的車位就增加一個;調(diào)用一次dispatch_semaphore_wait剩余車位就減少一個薄辅;
當剩余車位為0時要拂,再來車(即調(diào)用dispatch_semaphore_wait)就只能等待。有可能同時有幾輛車等待一個停車位站楚。有些車主
沒有耐心脱惰,給自己設定了一段等待時間,這段時間內(nèi)等不到停車位就走了窿春,如果等到了就開進去停車拉一。而有些車主就像把車停在這,
所以就一直等下去谁尸。
7.2 信號量的創(chuàng)建和使用
- 創(chuàng)建
dispatch_semaphore_create
/*!
* @function dispatch_semaphore_create
使用信號量來處理多個線程之間競爭資源的情況特別合適舅踪,在value等于0的時候進行等待,在value大于0的時候運行
*
* @param value
* 初始化創(chuàng)建的信號量的個數(shù)
*
* @result
* 當前創(chuàng)建的信號量
*/
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_semaphore_t
dispatch_semaphore_create(long value);
- 等待
dispatch_semaphore_wait
/*!
* @function dispatch_semaphore_wait
*
* @abstract
* 等待一個信號量
*
* @discussion
* 會對信號量進行-1,如果value小于0良蛮,接下來的方法會允許等待的時間里一直等待直到有其他線程有信號量產(chǎn)生即value>1 才開始執(zhí)行
*
* @param dsema
* The semaphore. 不允許設置為NULL
* @param timeout
* 允許等待的超時時間
* 一下兩個是宏定義的時間
* DISPATCH_TIME_NOW and DISPATCH_TIME_FOREVER constants.
*
*/
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);
- 產(chǎn)生
dispatch_semaphore_signal
/*!
* @function dispatch_semaphore_signal
*
* @abstract
* 對信號量增加1
*
* @discussion
* 對信號量增加1,如果之前的value==0,那么這個操作會喚醒一個正在等待的線程
*
* @param dsema The counting semaphore.
* The semaphore. 不允許設置為NULL
*/
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);
注意
dispatch_semaphore_signal
和 dispatch_semaphore_wait
必須成對出現(xiàn)抽碌,并且在 dispatch_release(aSemaphore)
;之前,aSemaphore
的value需要恢復之前的數(shù)值,不然會導致 EXC_BAD_INSTRUCTION
在ARC情況下不需要使用dispatch_release來進行釋放货徙,有系統(tǒng)統(tǒng)一管理
##7.3 `Dispatch Semaphore` 的應用
###7.3.1 控制并發(fā)線程數(shù)量
/*
實戰(zhàn)版本:具有專門控制并發(fā)等待的線程左权,優(yōu)點是不會阻塞主線程,可以跑一下 demo痴颊,你會發(fā)現(xiàn)主屏幕上的按鈕是可點擊的赏迟。但相應的,viewdidload 方法中的柵欄方法dispatch_barrier_async就失去了自己的作用:無法達到“等為數(shù)組遍歷添加元素后蠢棱,檢查下數(shù)組的成員個數(shù)是否正確”的效果锌杀。
*/
void dispatch_async_limit(dispatch_queue_t queue,NSUInteger limitSemaphoreCount, dispatch_block_t block) {
//控制并發(fā)數(shù)的信號量
static dispatch_semaphore_t limitSemaphore;
//專門控制并發(fā)等待的線程
static dispatch_queue_t receiverQueue;
//使用 dispatch_once而非 lazy 模式,防止可能的多線程搶占問題
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
limitSemaphore = dispatch_semaphore_create(limitSemaphoreCount);
receiverQueue = dispatch_queue_create("receiver", DISPATCH_QUEUE_SERIAL);
});
dispatch_async(receiverQueue, ^{
//可用信號量后才能繼續(xù)泻仙,否則等待
dispatch_semaphore_wait(limitSemaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
!block ? : block();
//在該工作線程執(zhí)行完成后釋放信號量
dispatch_semaphore_signal(limitSemaphore);
});
});
}
###7.3.2 為 NSURLSession 添加同步方法 在數(shù)據(jù)請求完成后才會返回
-
(NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse *__autoreleasing *)response error:(NSError *__autoreleasing *)error {
__block NSData *data = nil;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *taskData, NSURLResponse *taskResponse, NSError *taskError) {
data = taskData;if (response) *response = taskResponse; if (error) *error = taskError; dispatch_semaphore_signal(semaphore);
}] resume];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
return data;
}
但是也要思考下為什么 Apple 取消了同步方法:同步方法的風險遠遠超過受益糕再。
要注意:
- 除非萬不得已,否則永遠不要嘗試在主線程上發(fā)送同步的網(wǎng)絡請求
- 盡量只在后臺線程中獨占線程發(fā)送同步的網(wǎng)絡請求
風險如下所示:
###7.3.3 加鎖
阻塞線程玉转,直到value>1
lock = dispatch_semaphore_create(1);
dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
// 先從緩存中嘗試獲取
_YYModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
dispatch_semaphore_signal(lock);
#8. CGD Group的實踐
##8.1 用group wait來等待queue的一組任務
如果要等待queue中的一系列操作完成后再去執(zhí)行一個相應的任務突想,除了用barrier之外,我們也可以通過group來進行處理,dispatch group wait會阻塞當前的線程究抓,直到group中的任務完成才會停止阻塞猾担,這樣我們可以達到一個目的,直到前面的任務完成了刺下,才執(zhí)行后面的代碼
dispatch_queue_t queue = dispatch_queue_create("abc", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue,^{
NSLog(@"1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"3");
});
dispatch_group_async(group, queue, ^{
sleep(5);
NSLog(@"4");
});
// 開啟一個異步隊列來等待
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"done");
});
});
NSLog(@"主線程");
打印結(jié)果:
2016-07-08 15:12:45.188 dispatch_source[45653:1777587] 3
2016-07-08 15:12:45.188 dispatch_source[45653:1777585] 1
2016-07-08 15:12:45.188 dispatch_source[45653:1777586] 2
2016-07-08 15:12:45.188 dispatch_source[45653:1777495] 主線程
2016-07-08 15:12:50.189 dispatch_source[45653:1777617] 4
2016-07-08 15:12:50.189 dispatch_source[45653:1777618] done
##8.2 用group notify來實現(xiàn)等待queue的一組任務
用 `dispatch_group_notify`方法可以等待group中的任務绑嘹,notify中的任務在原來group中的任務執(zhí)行結(jié)束前不會執(zhí)行
dispatch_queue_t queue = dispatch_queue_create("abc", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue,^{
NSLog(@"1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"3");
});
dispatch_group_async(group, queue, ^{
sleep(5);
NSLog(@"4");
});
// 等待group中的任務執(zhí)行完成
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
NSLog(@"主線程");
打印結(jié)果
2016-07-08 15:25:04.207 dispatch_source[45904:1789795] 3
2016-07-08 15:25:04.207 dispatch_source[45904:1789794] 1
2016-07-08 15:25:04.207 dispatch_source[45904:1789793] 2
2016-07-08 15:25:04.208 dispatch_source[45904:1789768] 主線程
2016-07-08 15:25:09.211 dispatch_source[45904:1789798] 4
2016-07-08 15:25:09.212 dispatch_source[45904:1789798] done
##8.3 手動進入group
`dispatch_group_enter` 手動通知 Dispatch Group 任務已經(jīng)開始。你必須保證 `dispatch_group_enter` 和 `dispatch_group_leave` 成對出現(xiàn)怠李,否則你可能會遇到詭異的崩潰問題圾叼。
// 進入group
dispatch_group_enter(group);
** 加入組執(zhí)行的代碼 **
// 離開group
dispatch_group_leave(group)
##8.4 應用例子
1. 多線程下載9張小圖片蛤克,然后在都下載好后將9張小圖片合成一個大圖片
2. 監(jiān)測多個網(wǎng)絡請求的完成
# 9 Dispatch Source 分派源
dispatch source 是基礎數(shù)據(jù)類型,協(xié)調(diào)特定底層系統(tǒng)事件的處理捺癞。GCD 支持 以下 dispatch source:
- Timer dispatch source: 定期產(chǎn)生通知
- Signal dispatch source: UNIX 信號達到時產(chǎn)生通知
- Descriptor dispatch source: 各種文件和socket操作的通知
- 數(shù)據(jù)可讀
- 數(shù)據(jù)可寫
- 文件在文件系統(tǒng)中被刪除、移動构挤、重命名
- 文件元素信息gaibian
- Process dispatch source: 進程相關的事件通知
- 進程退出時
- 當進程發(fā)起fork或exec等調(diào)用
- 信號被遞送到進程
- Mach port dispatch source: Mach 相關事件的通知
- Custom dispatch source: 你自己定義并自己觸發(fā)
`Dispatch source` 替代了異步回調(diào)函數(shù),來處理系統(tǒng)相關的事件髓介。當你配置一個` dispatch source` 時,你指定要監(jiān)測的事件、`dispatch queue`筋现、以及處理事件的代 碼(block 或函數(shù))唐础。當事件發(fā)生時, `dispatch source` 會提交你的 block 或函數(shù)到 指定的 queue 去執(zhí)行。
和手工提交到 queue 的任務不同,dispatch source 為應用提供連續(xù)的事件源矾飞。 除非你顯式地取消,dispatch source 會一直保留與 dispatch queue 的關聯(lián)一膨。只要 相應的事件發(fā)生,就會提交關聯(lián)的代碼到 dispatch queue 去執(zhí)行。
為了防止事件積壓到 dispatch queue,dispatch source 實現(xiàn)了事件合并機制洒沦。
**如果新事件在上一個事件處理器出列并執(zhí)行之前到達,dispatch source 會將新舊事件的數(shù)據(jù)合并**豹绪。根據(jù)事件類型的不同,合并操作可能會替換舊事件,或者更新 舊事件的信息。
名稱 內(nèi)容
DISPATCH_SOURCE_TYPE_DATA_ADD 變量增加
DISPATCH_SOURCE_TYPE_DATA_OR 變量OR
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口發(fā)送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
DISPATCH_SOURCE_TYPE_PROC 監(jiān)測到與進程相關的事件
DISPATCH_SOURCE_TYPE_READ 可讀取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL 接收信號
DISPATCH_SOURCE_TYPE_TIMER 定時器
DISPATCH_SOURCE_TYPE_VNODE 文件系統(tǒng)有變更
DISPATCH_SOURCE_TYPE_WRITE 可寫入文件映像
##9.1 創(chuàng)建Dispatch Source
>分派源提供了高效的方式來處理事件申眼。首先注冊事件處理程序瞒津,事件發(fā)生時會收到通知蝉衣。如果在系統(tǒng)還沒有來得及通知你之前事件就發(fā)生了多次,那么這些事件會被合并為一個事件巷蚪。
####**創(chuàng)建步驟**
1. 使用dispatch_source_create 函數(shù)創(chuàng)建 dispatch source
2. 配置dispatch source
- 為 dispatch source 設置一個Handle
- 對于定時器源,使用 dispatch_source_set_timer 函數(shù)設置定時器信
息
3. 為dispatchsource賦予一個取消處理器
4. 調(diào)用 dispatch_resume 函數(shù)開始處理事件
//1. 使用dispatch_source_create 函數(shù)創(chuàng)建 dispatch source
// 指定DISPATCH_SOURCE_TYPE_DATA_ADD病毡,做成Dispatch Source(分派源)。設定Main Dispatch Queue 為追加處理的Dispatch Queue
_processingQueueSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0,
dispatch_get_main_queue());
__block NSUInteger totalComplete = 0;
// 2 配置dispatch source
dispatch_source_set_event_handler(_processingQueueSource, ^{
***執(zhí)行的代碼***
});
// 4. 調(diào)用 dispatch_resume 函數(shù)開始處理事件
// 分派源創(chuàng)建時默認處于暫停狀態(tài)屁柏,在分派源分派處理程序之前必須先恢復啦膜。
dispatch_resume(_processingQueueSource);
##9.2 暫停、恢復與取消操作
需要記得的一點是分派源創(chuàng)建時默認處于暫停狀態(tài)淌喻,在分派源分派處理程序之前必須先恢復功戚。
`dispatch_suspend(queue)`: 用來對分派源進行暫停操作,只能暫停還沒執(zhí)行的任務似嗤,如果已經(jīng)開始執(zhí)行的任務不會受到影響
`dispatch_resume (source)`: 對暫停的分派源進行恢復操作
暫停一個 dispatch source 期間,發(fā)生的任何事件都會被累積,直到 dispatch source 繼續(xù)啸臀。但是不會遞送所有事件,而是先合并到單一事件,然后再一次遞送。dispatch source中的handler的block會暫停執(zhí)行烁落,而 `dispatch_source_merge_data`會繼續(xù)發(fā)送消息乘粒,只是handler不會被掛起不去處理,一旦恢復伤塌,handler會馬上收到 `dispatch_source_merge_data` 發(fā)送來的所有消息
`dispatch_source_cancel(source)`: 除非你顯式地調(diào)用 `dispatch_source_cancel` 函數(shù),dispatch source 將一直保持 活動,取消一個 dispatch source 會停止遞送新事件,并且不能撤銷灯萍。
##9.4 Dispatch Source 和 **Dispatch Queue** 兩者在線程執(zhí)行上的關系
兩者線程上沒有關系,獨立運行每聪。 **Dispatch Queue** 像一個生產(chǎn)任務的生產(chǎn)者旦棉,而 **Dispatch Source** 像處理任務的消費者∫┦恚可以一邊異步生產(chǎn)绑洛,也可一邊異步消費。你可以在任意線程上調(diào)用 `dispatch_source_merge_data` 以觸發(fā) `dispatch_source_set_event_handler` 童本。而句柄的執(zhí)行線程真屯,取決于你創(chuàng)建句柄時所指定的線程。
自定義源也需要一個隊列穷娱,用來處理所有的響應句柄(block)绑蔫。那么豈不是有兩個隊列了?沒錯泵额,一個隊列用來執(zhí)行自定義源配深,一個用來執(zhí)行句柄
##9.5 Dispatch Source 能通過合并事件來確保高負載下正常工作
在同一時間,只有一個處理 block 的實例被分配嫁盲,如果這個處理方法還沒有執(zhí)行完畢篓叶,另一個事件就發(fā)生了,事件會以指定方式(ADD或 OR)進行累積。DispatchSource能通過合并事件(block)的方式確保在高負載下正常工作澜共。
##9.6 Dispatch Source 的實際應用
###9.6.1 Dispatch Source 實現(xiàn)GCD中隊列任務的終止
>實際上 Dispatch Queue 沒有“取消”這一概念向叉。一旦將處理追加到 Dispatch Queue 中,就沒有方法可將該處理去除嗦董,也沒有方法可在執(zhí)行中取消該處理母谎。編程人員要么在處理中導入取消這一概念。
要么放棄取消京革,或者使用 NSOperationQueue 等其他方法奇唤。
Dispatch Source 與 Dispatch Queue 不同,是可以取消的匹摇。而且取消時必須執(zhí)行的處理可指定為回調(diào)用的Block形式咬扇。
###9.6.2 使用Dispatch Queue 來取代NSTimer
眾所周知,定時器有NSTimer廊勃,但是NSTimer有如下弊端:
1. 必須保證有一個活躍的runloop懈贺,子線程的runloop是默認關閉的。這時如果不手動激活runloop坡垫,performSelector和scheduledTimerWithTimeInterval的調(diào)用將是無效的
2. NSTimer的創(chuàng)建與撤銷必須在同一個線程操作梭灿、performSelector的創(chuàng)建與撤銷必須在同一個線程操作。
3. 內(nèi)存管理有潛在泄露的風險會造成循環(huán)引用
所以我們可以使用 `Dispatch Source` 的 `DISPATCH_SOURCE_TYPE_TIMER` 來實現(xiàn)這個效果
-
(void) startGCDTimer{
NSTimeInterval period = 1.0; //設置時間間隔
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒執(zhí)行
dispatch_source_set_event_handler(_timer, ^{
//在這里執(zhí)行事件
NSLog(@"每秒執(zhí)行test");
});dispatch_resume(_timer);
}
-(void) pauseTimer{
if(_timer){
dispatch_suspend(_timer);
}
}
-(void) resumeTimer{
if(_timer){
dispatch_resume(_timer);
}
}
-(void) stopTimer{
if(_timer){
dispatch_source_cancel(_timer);
_timer = nil;
}
}
###9.6.3 監(jiān)控文件系統(tǒng)對象
設置`DISPATCH_SOURCE_TYPE_VNODE` 類型的Dispatch Source冰悠,可以中這個 Source 中接收文件刪除堡妒、寫入、重命名等通知溉卓。
int fd = open(filename, O_EVTONLY);
if (fd == -1)
return NULL;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
, DISPATCH_VNODE_RENAME, queue);
if (source)
{
// 保持文件名
int length = strlen(filename);
char* newString = (char)malloc(length + 1);
newString = strcpy(newString, filename);
dispatch_set_context(source, newString);
// 設置Source的handler 來對監(jiān)測文件修改的處理
dispatch_source_set_event_handler(source, ^{
const char oldFilename = (char*)dispatch_get_context(source);
MyUpdateFileName(oldFilename, fd);
});
// 做釋放source之前的處理 關閉文件
dispatch_source_set_cancel_handler(source, ^{
char* fileStr = (char*)dispatch_get_context(source); free(fileStr);
close(fd);
});
// 開始執(zhí)行start
dispatch_resume(source);
}
###9.6.4 監(jiān)測進程的變化
進程 dispatch source 可以監(jiān)控特定進程的行為,并適當?shù)仨憫こ佟8高M程可以 使用 dispatch source 來監(jiān)控自己創(chuàng)建的所有子進程,例如監(jiān)控子進程的死亡;類似地,子進程也可以使用 dispatch source 來監(jiān)控父進程,例如在父進程退出時自 己也退出。
dispatch_queue_t queue = dispatch_get_main_queue();
static dispatch_source_t source = nil;
__typeof(self) __weak weakSelf = self;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGSTOP, 0, queue);
if (source){
dispatch_source_set_event_handler(source, ^{
NSLog(@"Hi, I am: %@", weakSelf);
});
dispatch_resume(source);
}
});
可以在上面看到實現(xiàn)的結(jié)果桑寨,當執(zhí)行的時候打斷點伏尼,進程發(fā)現(xiàn)變化彩繪進去打印 `Hi, I am:XXXXX`這句話
###9.6.5 實現(xiàn)對搜索的截流(還沒看源代碼,等看完分析)
#10 dispatch_barrier 柵欄
`dispatch_barrier` 最大的作用就是用來做**阻塞**西疤,阻塞當前的線程烦粒,做到一個**承上啟下**的作用,只有在它之前的任務全部執(zhí)行完之后代赁,它和它之后的任務才能進行∈揸可以理解為成語**一夫當關芭碍,萬夫莫開**,只有在它面前的任務“死掉了”(即執(zhí)行完了)后面的任務才能繼續(xù)進行下去孽尽。
![](./1467981139516.png)
##10.1 使用注意
使用dispatch_barrier 是用來阻斷并行任務不能確定先后任務完成的問題窖壕,**它必須使用在自定義并行隊列上**,否則沒有意義,為什么說沒意義呢瞻讽,我們接下來分析為什么沒意義:
1. 如果運用在串行隊列上鸳吸,沒有意義,因為串行隊列本來就是先進先出的規(guī)則速勇,用了柵欄跟沒用沒有區(qū)別
2. 如果使用全局隊列晌砾,也是沒有意義,我們每次 **dispatch_get_global_queue** 獲取到的隊列都是不同的烦磁,我們?nèi)蝿涨昂髨?zhí)行不在同一個線程上养匈,也就沒有了截流之分。
##10.2 可寫對象的寫入安全問題
系統(tǒng)中提供的可變對象都是線程不安全的都伪,也就是在一個線程進行寫入數(shù)據(jù)的時候呕乎,不允許其他線程訪問,無論是讀或者是寫都是不允許的陨晶。
##10.2.1 使用dispatch_barrier來實現(xiàn)寫入安全
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dispatch_queue_t queue = dispatch_queue_create("iKingsly", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_async(queue, ^{
for (int i = 0; i < 1000; i++) {
dict[@(i)] = @(i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 1000; i++) {
NSLog(@"%@", dict[@(i)]);
}
});
##10.2.2 使用信號量來處理讀寫線程安全問題
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dispatch_queue_t queue = dispatch_queue_create("iKingsly", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
for (int i = 0; i < 1000; i++) {
dict[@(i)] = @(i);
}
dispatch_semaphore_signal(semaphore);
});
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
for (int i = 0; i < 1000; i++) {
NSLog(@"%@", dict[@(i)]);
}
dispatch_semaphore_signal(semaphore);
});
#參考鏈接
[《GCD實踐之二 -- 多用GCD猬仁,少用performSelector系列方法》](http://zhangbuhuai.com/using-gcd-part-2/)
[Parse源碼淺析系列(一)---Parse的底層多線程處理思路:GCD高級用法](https://github.com/ChenYilong/ParseSourceCodeStudy/blob/master/01_Parse的多線程處理思路/Parse的底層多線程處理思路.md)