1. 什么是GCD?
GCD 是 libdispatch 的市場名稱,而 libdispatch 作為 Apple 的一個庫,為并發(fā)代碼在多核硬件(跑 iOS 或 OS X )上執(zhí)行提供有力支持。它具有以下優(yōu)點:
GCD 能通過推遲昂貴計算任務(wù)并在后臺運行它們來改善你的應(yīng)用的響應(yīng)性能。
GCD 提供一個易于使用的并發(fā)模型而不僅僅只是鎖和線程,以幫助我們避開并發(fā)陷阱。
GCD 具有在常見模式(例如單例)上用更高性能的原語優(yōu)化你的代碼的潛在能力。
2. 并行與并發(fā)
簡單來說,若說兩個任務(wù)A和B并發(fā)執(zhí)行,則表示任務(wù)A和任務(wù)B在同一時間段里被執(zhí)行(當(dāng)計算機(jī)CPU只有一個核心在運行時,二者是交替執(zhí)行的,即切換上下文,CPU在兩個線程之間高速切換,讓人感覺這兩個任務(wù)是“同時”進(jìn)行的);若說任務(wù)A和B并行執(zhí)行,則表示任務(wù)A和任務(wù)B在同時被執(zhí)行(這要求計算機(jī)CPU具有多核運算能力,即這兩個任務(wù)分別在不同的核心上同時運行);
一句話:并行要求并發(fā),但并發(fā)并不能保證并行。
3. Dispatch Queues
Dispatch Queue是一個任務(wù)執(zhí)行隊列,可以讓你異步或同步地執(zhí)行多個Block或函數(shù)。Dispatch Queue是FIFO的,即先入隊的任務(wù)總會先執(zhí)行。目前有三種類型的Dispath Queue:
- 串行隊列(Serial dispatch queue)
- 并行隊列(Concurrent dispatch queue)
- 主隊列(Main dispatch queue)
3.1 串行隊列(Serial dispatch queue)
serial dispatch queue中的block按照先進(jìn)先出(FIFO)的順序去執(zhí)行,實際上為單線程執(zhí)行。即每次從queue中取出一個task進(jìn)行處理;用戶可以根據(jù)需要創(chuàng)建任意多的serial dispatch queue,serial dispatch queue彼此之間是并發(fā)的,創(chuàng)建一個串行隊列如下:
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MySerialQueue", DISPATCH_QUEUE_SERIAL);
3.2 并行隊列(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);
3.3 主隊列(Main dispatch queue)
application的主要任務(wù)(譬如UI管理之類的)都在main dispatch queue中完成;根據(jù)文檔的描述,main dispatch queue中的task都在一個thread中運行,即application’s main thread(thread 1)。
所以,如果想要更新UI,則必須在main dispatch queue中處理,獲取main dispatch queue也很容易,調(diào)用dispatch_get_main_queue()函數(shù)即可。
4. dispatch_sync和dispatch_async 同步和異步
dispatch_sync 派發(fā)的block的執(zhí)行線程和 dispatch_sync 上下文線程是同一個線程;
dispatch_async 派發(fā)的block的執(zhí)行線程和 dispatch_async 上下文線程不是同一個線程,即主隊列 下異步任務(wù)還是在主隊列下執(zhí)行;
對于serial dispatch queue中的tasks,無論是同步派發(fā)還是異步派發(fā),其執(zhí)行順序都遵循FIFO;
為了方便地使用 GCD,蘋果提供了一些方法方便我們將 block 放在主線程 或 后臺線程執(zhí)行,或者延后執(zhí)行。使用的例子如下:
// 后臺執(zhí)行:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// something
});
// 主線程執(zhí)行:
dispatch_async(dispatch_get_main_queue(), ^{
// something
});
// 一次性執(zhí)行:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// code to be executed once
});
// 延遲 2 秒執(zhí)行:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// code to be executed on the main queue after delay
});
5. 使用GCD來替代performSelector的原因:
5.1 performSelector 會導(dǎo)致內(nèi)存泄漏問題
用performSelector:調(diào)用了一個方法,編譯器并不知道將要調(diào)用的selector是什么,因此,也就不了解其方法簽名及返回值,甚至連是否有返回值都不清楚。而且,由于編譯器不知道方法名,所以就沒辦法用ARC的內(nèi)存管理規(guī)則來判定返回值是不是該釋放。鑒于此,ARC采用了比較謹(jǐn)慎的做法,就是不添加釋放操作。然而,這么做可能導(dǎo)致內(nèi)存泄漏,因為方法在返回對象時已經(jīng)將其保留了。
5.2 performSelector 返回值只能是void或?qū)ο箢愋停╥d類型)
如果想返回整數(shù)或浮點數(shù)等scalar類型值,那么就需要執(zhí)行一些復(fù)雜的轉(zhuǎn)換操作,而這種轉(zhuǎn)換操作很容易出錯。由于id類型表示指向任意Objective—C對象的指針,所以從技術(shù)上來講,只要返回的大小和指針?biāo)即笮∠嗤托校簿褪钦f,在32位架構(gòu)的計算機(jī)上,可以返回任意32位大小的類型;而在64位架構(gòu)的計算機(jī)上,則可以返回任意64位大小的類型。除此之外,還可以返回NSNumber進(jìn)行轉(zhuǎn)換…若返回的類型為C語言結(jié)構(gòu)體,則不可使用performSelector方法。
5.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的可能性。
6. GCD 中block的使用
6.1 GCD 會對添加的Block進(jìn)行復(fù)制
Dispatchqueue對添加的Block會進(jìn)行復(fù)制,在完成執(zhí)行后自動釋放。換句話說,你不需要在添加 Block 到 Queue 時顯式地復(fù)制
6.2 GCD 中的autorelease pool
GCD dispatch queue 有自己的autorelease pool來管理內(nèi)存對象,但是不保證在什么時候會進(jìn)行回收,如果在block中創(chuàng)建了大量的對象,可以添加自己的autorelease pool來進(jìn)行管理。
6.3 GCD 中再開新的線程執(zhí)行任務(wù)不一定更快
如果對于工作量小的block切換線程的開銷,比直接在原來線程上執(zhí)行block的開銷要大,那么這樣的話,會導(dǎo)致開新的線程反而沒有原來執(zhí)行的快,也就是說誰開銷大誰慢。
6.4 GCD 的暫停和繼續(xù)
1、dispatch_suspend 會暫停一個隊列以阻止執(zhí)行block對象,調(diào)用 dispatch_suspend 會增加queue的引用計數(shù)
2、dispatch_resume 會使得隊列恢復(fù)繼續(xù)執(zhí)行block對象,調(diào)用 dispatch_resume 會減少queue的引用計數(shù)
掛起和繼續(xù)是異步的,只在沒有執(zhí)行的block上生效,掛起一個block不會導(dǎo)致已經(jīng)開始執(zhí)行的block停止執(zhí)行。
7. GCD中的信號量 Semaphore
7.1 信號量概念
停車場剩余4個車位,那么即使同時來了四輛車也能停的下。如果此時來了五輛車,那么就有一輛需要等待。信號量的值就相當(dāng)于剩余車位的數(shù)目,dispatch_semaphore_wait函數(shù)就相當(dāng)于來了一輛車,dispatch_semaphore_signal,就相當(dāng)于走了一輛車。停車位的剩余數(shù)目在初始化的時候就已經(jīng)指明了(dispatch_semaphore_create(long value))調(diào)用一次dispatch_semaphore_signal,剩余的車位就增加一個;調(diào)用一次dispatch_semaphore_wait剩余車位就減少一個;當(dāng)剩余車位為0時,再來車(即調(diào)用dispatch_semaphore_wait)就只能等待。有可能同時有幾輛車等待一個停車位。有些車主沒有耐心,給自己設(shè)定了一段等待時間,這段時間內(nèi)等不到停車位就走了,如果等到了就開進(jìn)去停車。而有些車主就要把車停在這,所以就一直等下去。
7.2 信號量的創(chuàng)建和使用
1、創(chuàng)建 dispatch_semaphore_create
/*!
* @function dispatch_semaphore_create
使用信號量來處理多個線程之間競爭資源的情況特別合適,在value等于0的時候進(jìn)行等待,在value大于0的時候運行
*
* @param value
* 初始化創(chuàng)建的信號量的個數(shù)
*
* @result
* 當(dāng)前創(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);
2、等待 dispatch_semaphore_wait
/*!
* @function dispatch_semaphore_wait
*
* @abstract
* 等待一個信號量
*
* @discussion
* 會對信號量進(jìn)行-1,如果value小于0,接下來的方法會允許等待的時間里一直等待直到有其他線程有信號量產(chǎn)生即 value>1 才開始執(zhí)行
*
* @param dsema
* The semaphore. 不允許設(shè)置為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);
3、產(chǎn)生 dispatch_semaphore_signal
/*!
* @function dispatch_semaphore_signal
*
* @abstract
* 對信號量增加1
*
* @discussion
* 對信號量增加1,如果之前的value==0,那么這個操作會喚醒一個正在等待的線程
*
* @param dsema The counting semaphore.
* The semaphore. 不允許設(shè)置為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需要恢復(fù)之前的數(shù)值,不然會導(dǎo)致 EXC_BAD_INSTRUCTION
在ARC情況下不需要使用dispatch_release來進(jìn)行釋放,有系統(tǒng)統(tǒng)一管理
7.3 Dispatch Semaphore 的應(yīng)用
1、控制并發(fā)線程數(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);
});
});
}
2、等待某個網(wǎng)絡(luò)回調(diào)完之后才執(zhí)行后面的操作
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[NetWorkkService queryCompletion:^(BOOL isSuccess) {
dispatch_semaphore_signal(sema);
} onError:^(int errorCode, NSString *errorMessage) {
dispatch_semaphore_signal(sema);
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
// todo what you want to do after net callback
3、使用信號量來處理讀寫線程安全問題
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);
});
8. CGD Group
8.1 用group wait來等待queue的一組任務(wù)
如果要等待queue中的一系列操作完成后再去執(zhí)行一個相應(yīng)的任務(wù),除了用barrier之外,我們也可以通過group來進(jìn)行處理,dispatch group wait會阻塞當(dāng)前的線程,直到group中的任務(wù)完成才會停止阻塞,這樣我們可以達(dá)到一個目的,直到前面的任務(wù)完成了,才執(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(@"主線程");
log打印如下:
2017-03-22 22:43:59.946 ForDemo[2196:421544] 1
2017-03-22 22:43:59.946 ForDemo[2196:421543] 2
2017-03-22 22:43:59.946 ForDemo[2196:421546] 3
2017-03-22 22:43:59.946 ForDemo[2196:421405] 主線程
2017-03-22 22:44:05.018 ForDemo[2196:421549] 4
2017-03-22 22:44:05.018 ForDemo[2196:421549] done
8.2 用group notify來實現(xiàn)等待queue的一組任務(wù)
用 dispatch_group_notify方法可以等待group中的任務(wù),notify中的任務(wù)在原來group中的任務(wù)執(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中的任務(wù)執(zhí)行完成
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
NSLog(@"主線程");
log打印如下:
2017-03-22 22:48:28.632 ForDemo[2280:446210] 2
2017-03-22 22:48:28.632 ForDemo[2280:446146] 主線程
2017-03-22 22:48:28.632 ForDemo[2280:446196] 1
2017-03-22 22:48:28.632 ForDemo[2280:446197] 3
2017-03-22 22:48:33.698 ForDemo[2280:446199] 4
2017-03-22 22:48:33.698 ForDemo[2280:446199] done
8.3 手動進(jìn)入group
dispatch_group_enter 手動通知 Dispatch Group 任務(wù)已經(jīng)開始。你必須保證 dispatch_group_enter 和 dispatch_group_leave 成對出現(xiàn),否則你可能會遇到詭異的崩潰問題。
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(5);
NSLog(@"任務(wù)一完成");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
sleep(8);
NSLog(@"任務(wù)二完成");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"任務(wù)完成");
});
log打印如下:
2017-03-22 23:04:31.093 ForDemo[2476:497726] 任務(wù)一完成
2017-03-22 23:04:34.086 ForDemo[2476:497725] 任務(wù)二完成
2017-03-22 23:04:34.086 ForDemo[2476:497725] 任務(wù)完成
9. Dispatch Source
9.1 它有什么用?
dispatch source 的作用是負(fù)責(zé)監(jiān)聽事件,先看看它的構(gòu)造函數(shù)。
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t queue);
第1個參數(shù):要監(jiān)聽的事件類型
第2個參數(shù):可以理解為句柄、索引或id,假如要監(jiān)聽進(jìn)程,需要傳入進(jìn)程的ID
第3個參數(shù):根據(jù)參數(shù)2,可以理解為描述,提供更詳細(xì)的描述,讓它知道具體要監(jiān)聽什么
第4個參數(shù):當(dāng)事件發(fā)生時,將block添加至哪個隊列來執(zhí)行
9.2 可監(jiān)聽事件的類型
DISPATCH_SOURCE_TYPE_TIMER 定時響應(yīng)
DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信號時響應(yīng)
DISPATCH_SOURCE_TYPE_READ IO操作,如對文件的操作、socket操作的讀響應(yīng)
DISPATCH_SOURCE_TYPE_WRITE IO操作,如對文件的操作、socket操作的寫響應(yīng)
DISPATCH_SOURCE_TYPE_VNODE 文件狀態(tài)監(jiān)聽,文件被刪除、移動、重命名
DISPATCH_SOURCE_TYPE_PROC 進(jìn)程監(jiān)聽,如進(jìn)程的退出、創(chuàng)建一個或更多的子線程、進(jìn)程收到UNIX信號
DISPATCH_SOURCE_TYPE_MACH_SEND
DISPATCH_SOURCE_TYPE_MACH_RECV 上面2個都屬于Mach相關(guān)事件響應(yīng)
DISPATCH_SOURCE_TYPE_DATA_ADD
DISPATCH_SOURCE_TYPE_DATA_OR 上面2個都屬于自定義的事件,并且也是有自己來觸發(fā)
9.3 怎么使用
1、ADD類型事件監(jiān)聽
自定義事件(DISPATCH_SOURCE_TYPE_DATA_ADD、DISPATCH_SOURCE_TYPE_DATA_OR),先看代碼
dispatch_source_t source =dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(source,^{
NSLog(@"監(jiān)聽函數(shù):%lu",dispatch_source_get_data(source));
});
dispatch_resume(source);
dispatch_queue_t myqueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);
dispatch_async(myqueue, ^ {
int i;
for(i = 0;i < 4;i++){
dispatch_source_merge_data(source,i);
}
});
首先使用dispatch_source_create函數(shù)創(chuàng)建 dispatchsource,第1個參數(shù)表示它是一個自定義的_ADD類型的監(jiān)聽,具體作用后面說,2、3參數(shù)這里面沒有作用設(shè)置為0即可,第4個參數(shù) 表示一旦事件觸發(fā)就將要執(zhí)行的代碼塊添加到主隊列中執(zhí)行,接著我們使用dispatch_source_set_event_handler函數(shù)為這個監(jiān) 聽設(shè)置事件的源和響應(yīng)體,第1個參數(shù)表示這個監(jiān)聽是響應(yīng)用戶自定義事件的,也就是我們上面定義的dispatchsource,第2個參數(shù)是負(fù)責(zé)響應(yīng)的代 碼塊。很有意思的是當(dāng)我們創(chuàng)建監(jiān)聽后,這個監(jiān)聽默認(rèn)是掛起的,需要手動恢復(fù),所以我們使用dispatch_resume函數(shù)恢復(fù)這個監(jiān)聽,為了測試這個監(jiān)聽,我們后面又通過for循環(huán)觸發(fā)事件,觸發(fā)事件的函數(shù)就是dispatch_source_merge_data,這個函數(shù)負(fù)責(zé)觸發(fā)自定義事件,第1 個參數(shù)表示要觸發(fā)哪個監(jiān)聽,第2個參數(shù)是向監(jiān)聽傳入一個unsigned long 類型的值, 我們這里傳入循環(huán)的索引,好了,整體來看這段程序,dispatch_source_merge_data函數(shù)會被執(zhí)行4次,并分別傳入0、1、2、3這 4個值,既然dispatch_source_merge_data負(fù)責(zé)觸發(fā)事件,那么我們在監(jiān)聽里面的響應(yīng)體應(yīng)該會監(jiān)聽到,結(jié)果也確實監(jiān)聽到了,但是并不是我們想象的那樣打印4次,而是只打印了一次,打印結(jié)果是4次傳入值相加的和,也就是6,這就是 DISPATCH_SOURCE_TYPE_DATA_ADD參數(shù)的作用,這個監(jiān)聽在創(chuàng)建之初就被設(shè)置為自定義監(jiān)聽,并且會把監(jiān)聽結(jié)果相加,然后統(tǒng)一響 應(yīng)。這里你應(yīng)該會奇怪,既然結(jié)果會相加并統(tǒng)一響應(yīng),那跟觸發(fā)的時候加好,然后觸發(fā)一次有什么區(qū)別呢,好吧,我們把觸發(fā)事件的for循環(huán)改一下,然后再運 行,看看會發(fā)生什么
for(i = 0;i < 4;i++){
dispatch_source_merge_data(source,i);
[NSThread sleepForTimeInterval:0.0001];
}
我們在觸發(fā)事件的地方加上0.0001秒的延遲,然后運行整個程序多次,你會發(fā)現(xiàn)奇怪 的現(xiàn)象,我們同樣是觸發(fā)4次事件,但是響應(yīng)的次數(shù)變成不確定了,可能是1次,也可能是2次,如果你將延遲時間設(shè)置長點,甚至設(shè)置為0點幾秒就能讓響應(yīng)的次 數(shù)變?yōu)楣潭ǖ?次,為什么會這樣呢,其實這就是這個自定義事件設(shè)計的初衷。如果同一時間同一個事件被觸發(fā)的頻率非常密集,那么 dispatchsource會將這些密集的響應(yīng)相加統(tǒng)計做出響應(yīng),但是如果觸發(fā)的相對零散,那么dispatch source會分別進(jìn)行響應(yīng),這其實是在智能的控制UI的沒必要的更新操作,因為那些幾乎在同一時間更新進(jìn)度條的操作完全可以統(tǒng)一進(jìn)行更新,沒有必要每次 都更新一下。這樣做也會減少UI線程的負(fù)擔(dān),例如更新進(jìn)度條的同時,你的UI可能還在同時響應(yīng)用戶的輸入、觸碰等工作。當(dāng)然你可以選擇實時更新,辦法就是 直接使用使用dispatch_async直接更新界面。Dispatch source在統(tǒng)一響應(yīng)完畢后計數(shù)變?yōu)?,后面再觸發(fā)的會重新相加。DISPATCH_SOURCE_TYPE_DATA_OR會將所有監(jiān)聽到的值邏輯與操作,然后統(tǒng)一觸發(fā)。貌似沒有DISPATCH_SOURCE_TYPE_DATA_ADD常用
2、使用Dispatch Queue 來取代NSTimer
眾所周知,定時器有NSTimer,但是NSTimer有如下弊端:
必須保證有一個活躍的runloop,子線程的runloop是默認(rèn)關(guān)閉的。這時如果不手動激活runloop,performSelector和scheduledTimerWithTimeInterval的調(diào)用將是無效的。
NSTimer的創(chuàng)建與撤銷必須在同一個線程操作、performSelector的創(chuàng)建與撤銷必須在同一個線程操作。
內(nèi)存管理有潛在泄露的風(fēng)險會造成循環(huán)引用
所以我們可以使用 Dispatch Source 的 DISPATCH_SOURCE_TYPE_TIMER 來實現(xiàn)這個效果:
- (void) startGCDTimer{
NSTimeInterval period = 1.0; //設(shè)置時間間隔
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;
}
}
3、監(jiān)控文件系統(tǒng)對象
設(shè)置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);
// 設(shè)置Source的handler 來對監(jiān)測文件修改的處理
dispatch_source_set_event_handler(source, ^{
const char* oldFilename = (char*)dispatch_get_context(source);
MyUpdateFileName(oldFilename, fd);
});
// 做釋放source之前的處理 關(guān)閉文件
dispatch_source_set_cancel_handler(source, ^{
char* fileStr = (char*)dispatch_get_context(source); free(fileStr);
close(fd);
});
// 開始執(zhí)行start
dispatch_resume(source);
}
4、監(jiān)測進(jìn)程的變化
進(jìn)程 dispatch source 可以監(jiān)控特定進(jìn)程的行為,并適當(dāng)?shù)仨憫?yīng)。父進(jìn)程可以使用 dispatch source 來監(jiān)控自己創(chuàng)建的所有子進(jìn)程,例如監(jiān)控子進(jìn)程的死亡;類似地,子進(jìn)程也可以使用 dispatch source 來監(jiān)控父進(jìn)程,例如在父進(jìn)程退出時自己也退出。
NSRunningApplication *mail = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.mail"];
if (mail == nil) {
return;
}
pid_t const pid = mail.processIdentifier;
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(self.source, ^(){
NSLog(@"Mail quit.");
});
//在事件源傳到你的事件處理前需要調(diào)用dispatch_resume()這個方法
dispatch_resume(self.source);
5、監(jiān)視文件夾內(nèi)文件變化
NSURL *directoryURL; // assume this is set to a directory
int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);
if (fd < 0) {
char buffer[80];
strerror_r(errno, buffer, sizeof(buffer));
NSLog(@"Unable to open \"%@\": %s (%d)", [directoryURL path], buffer, errno);
return;
}
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
unsigned long const data = dispatch_source_get_data(source);
if (data & DISPATCH_VNODE_WRITE) {
NSLog(@"The directory changed.");
}
if (data & DISPATCH_VNODE_DELETE) {
NSLog(@"The directory has been deleted.");
}
});
dispatch_source_set_cancel_handler(source, ^(){
close(fd);
});
self.source = source;
dispatch_resume(self.source);
//還要注意需要用DISPATCH_VNODE_DELETE 去檢查監(jiān)視的文件或文件夾是否被刪除,如果刪除了就停止監(jiān)聽
10. Dispatch Barrier
dispatch_barrier 最大的作用就是用來做阻塞,阻塞當(dāng)前的線程,做到一個承上啟下的作用,只有在它之前的任務(wù)全部執(zhí)行完之后,它和它之后的任務(wù)才能進(jìn)行。可以理解為成語一夫當(dāng)關(guān),萬夫莫開,只有在它面前的任務(wù)“死掉了”(即執(zhí)行完了)后面的任務(wù)才能繼續(xù)進(jìn)行下去。
使用dispatch_barrier 是用來阻斷并行任務(wù)不能確定先后任務(wù)完成的問題,它必須使用在自定義并行隊列上,否則沒有意義,為什么說沒意義呢,我們接下來分析為什么沒意義:
如果運用在串行隊列上,沒有意義,因為串行隊列本來就是先進(jìn)先出的規(guī)則,用了柵欄跟沒用沒有區(qū)別
如果使用全局隊列,也是沒有意義,我們每次 dispatch_get_global_queue 獲取到的隊列都是不同的,我們?nèi)蝿?wù)前后執(zhí)行不在同一個線程上,也就沒有了截流之分。
系統(tǒng)中提供的可變對象都是線程不安全的,也就是在一個線程進(jìn)行寫入數(shù)據(jù)的時候,不允許其他線程訪問,無論是讀或者是寫都是不允許的。使用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++) {//這邊寫入完成,下面才能開始讀字典里的數(shù)據(jù)
dict[@(i)] = @(i);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 1000; i++) {
NSLog(@"%@", dict[@(i)]);
}
});
11. GCD死鎖
當(dāng)前串行隊列里面同步執(zhí)行當(dāng)前串行隊列就會死鎖,解決的方法就是將同步的串行隊列放到另外一個線程就能夠解決。
- (void)deadLockCase1 {
NSLog(@"1");
//主隊列的同步線程,按照FIFO的原則(先入先出),2排在3后面會等3執(zhí)行完,但因為同步線程,3又要等2執(zhí)行完,相互等待成為死鎖。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
}
- (void)deadLockCase2 {
NSLog(@"1");
//3會等2,因為2在全局并行隊列里,不需要等待3,這樣2執(zhí)行完回到主隊列,3就開始執(zhí)行
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSLog(@"2");
});
NSLog(@"3");
}
- (void)deadLockCase3 {
dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
NSLog(@"2");
//串行隊列里面同步一個串行隊列就會死鎖
dispatch_sync(serialQueue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- (void)deadLockCase4 {
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"2");
//將同步的串行隊列放到另外一個線程就能夠解決
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
- (void)deadLockCase5 {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"1");
//回到主線程發(fā)現(xiàn)死循環(huán)后面就沒法執(zhí)行了
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2");
});
NSLog(@"3");
});
NSLog(@"4");
//死循環(huán)
while (1) {
//
}
}
12. GCD實際使用
1、FMDB如何使用dispatch_queue_set_specific和dispatch_get_specific來防止死鎖,作用類似objc_setAssociatedObject跟objc_getAssociatedObject
static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
//創(chuàng)建串行隊列,所有數(shù)據(jù)庫的操作都在這個隊列里
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
//標(biāo)記隊列
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);
//檢查是否是同一個隊列來避免死鎖的方法
- (void)inDatabase:(void (^)(FMDatabase *db))block {
FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
}
2、DTCoreText使用GCD加快解析速度
DTCoreText采用的是SAX解析,iOS自帶了XML/HTML的解析引擎libxml,提供了兩個解析接口,DOM解析和SAX解析,前者使用簡單但是占用內(nèi)存多,SAX解析由于不會返回一個dom樹,采用的是查到一個標(biāo)簽比如回調(diào)startElement方法碰到內(nèi)容就回調(diào)_characters碰到類似就回調(diào)endElement這樣的方式。
根據(jù)這種解析方式DTCoreText使用多線程解析能夠更快的解析,DTHTMLAttributedStringBuilder使用三個dispatch_queue
_dataParsingQueue:解析html的
_treeBuildingQueue:生成dom樹的
_stringAssemblyQueue:組裝NSAttributeString的 獲取三個隊列全部完成采用了dispatch_group的dispatch_group_wait這種阻塞同步方式來返回結(jié)果。