GCD是異步執(zhí)行任務(wù)的技術(shù)支之一诸尽,開發(fā)者只需要將想要執(zhí)行的block任務(wù)添加到適當(dāng)?shù)?code>Dispatch Queue(調(diào)度隊(duì)列)里为障,GCD就能生成必要的線程并計(jì)劃地執(zhí)行任務(wù)羞芍。由于GCD線程管理是作為系統(tǒng)的一部分實(shí)現(xiàn)的熔萧,因此可統(tǒng)一管理,這樣就比以前的線程更有效率车猬。下面的例子代碼在這里门躯。
Dispatch Queue
Dispatch Queue
是執(zhí)行任務(wù)的等待隊(duì)列淆党,添加到Dispatch Queue
的任務(wù)按照FIFO(先進(jìn)先出)執(zhí)行處理。GCD里存在兩中Dispatch Queue
:
-
Serial Dispatch Queue
: 串行隊(duì)列。使用一個(gè)線程串行執(zhí)行添加到其中的任務(wù)宁否。 -
Concurrent Dispatch Queue
: 并行隊(duì)列窒升。使用多個(gè)線程并行執(zhí)行添加到其中的任務(wù)。
獲取這兩種隊(duì)列的方法有兩種:
- 通過
dispatch_queue_create(const char *label, <#dispatch_queue_attr_t attr#>)
函數(shù)可生成Dispatch Queue慕匠。第一個(gè)參數(shù)是給queue指定一個(gè)標(biāo)識(shí)饱须,該標(biāo)識(shí)在instrument和崩潰時(shí)會(huì)顯示,推薦使用的格式是 (com.example.myqueue) 台谊,可以設(shè)置為NULL蓉媳。第二個(gè)參數(shù)設(shè)置為DISPATCH_QUEUE_SERIAL
是生成Serial Dispatch Queue,設(shè)置為DISPATCH_QUEUE_CONCURRENT
是生成Concurrent Dispatch Queue锅铅。在iOS6之前酪呻, Dispatch Queue需要程序員自己去釋放,使用dispatch_release()
盐须。
block被提交到queue后玩荠,會(huì)持有queue的引用計(jì)數(shù),直到block完成為止贼邓,一旦queue的所有引用都釋放后阶冈,queue將會(huì)被系統(tǒng)dealloc。上面代碼中的queue 雖然在添加了block之后立即release了塑径,但是block已經(jīng)持有了queue,所以等到block執(zhí)行后queue才會(huì)釋放女坑。dispatch_queue_t queue = dispatch_queue_create("come.example.gcd.mySerialDispatchQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(@"fsdfs"); }); dispatch_release(queue);
- 獲取系統(tǒng)提供的Dispatch Queue。
系統(tǒng)提供了dispatch_get_main_queue()
,這個(gè)是主線程统舀,是Serial Dispatch Queue匆骗。
Global Dispatch Queue 是所有應(yīng)用程序都能夠使用的Concurrent Dispatch Queue;需要使用并行隊(duì)列時(shí)誉简,可以不用通過dispatch_queue_create
創(chuàng)建Concurrent Dispatch Queue碉就,直接獲取Global Dispatch Queue即可。
控制臺(tái)打印的結(jié)果dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue1, ^{ NSLog(@"queue1 block1 %@", [NSThread currentThread]); }); dispatch_async(queue1, ^{ NSLog(@"queue1 block2 %@", [NSThread currentThread]); });
queue1這個(gè)隊(duì)列里系統(tǒng)自動(dòng)生成了兩個(gè)線程去執(zhí)行block任務(wù)闷串。queue1 block1 <NSThread: 0x60000007fc80>{number = 3, name = (null)} queue1 block2 <NSThread: 0x60800026ab80>{number = 5, name = (null)}
Global Dispatch Queue有四種優(yōu)先級(jí)#define DISPATCH_QUEUE_PRIORITY_HIGH 2 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
Dispatch async 和 sycn
dispatch_asycn函數(shù)的"asycn"意味著異步铝噩,就是將block追加到指定的queue中后,dispatch_asycn函數(shù)立即返回窿克,不會(huì)等待執(zhí)行的結(jié)果。
dispatch_sycn函數(shù)意味著同步毛甲,也就是將block函數(shù)追加到queue中后年叮,在block任務(wù)執(zhí)行完之前,dispatch_sycn函數(shù)會(huì)一直等待玻募。
dispatch_sycn函數(shù)使用簡(jiǎn)單只损,所以也容易引起死鎖問題,如果dispatch_sycn把blcok提交到當(dāng)前的queue里,并且當(dāng)前的隊(duì)列是串行隊(duì)列跃惫,會(huì)造成死鎖叮叹。例如在主線程中執(zhí)行以下代碼就會(huì)造成死鎖:
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"hello world");
});
在主線程里調(diào)用上面的方法,但是又把block提交到了主線程里爆存,主線程要等待dispatch_syn函數(shù)返回后才能執(zhí)行block中的代碼蛉顽,dispatch_sycn函數(shù)又要等到主線程執(zhí)行完block后才能返回,因此造成了死鎖先较。下面的例子也是一樣的:
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
dispatch_sync(mainQueue, ^{
NSLog(@"hello world");
});
});
在串行隊(duì)列中也會(huì)有同樣的問題:
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.serialQueue", NULL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue, ^{
NSLog(@"hello world");
});
});
nslog不會(huì)被執(zhí)行携冤。
在并行隊(duì)列中使用dispatch_async是不會(huì)出現(xiàn)死鎖的。
Dispatch Group
經(jīng)常會(huì)碰到這種情況闲勺,想要在加入到Dispatch Queue中的多個(gè)block任務(wù)都執(zhí)行完后去執(zhí)行其他任務(wù)曾棕,如果使用的是串行隊(duì)列,只要將所有的block任務(wù)加入到串行隊(duì)列并在最后追加其他任務(wù)即可菜循;如果使用的是并行隊(duì)列或者有多個(gè)DIspatch Queue時(shí)翘地,可以使用Dispatch Group。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block3");
});
dispatch_group_notify(group, queue, ^{
NSLog(@"done");
});
在上面的示例中dispatch_group_async(<#dispatch_group_t group#>, dispatch_queue_t queue, <#^(void)block#>)
是將block提交到queue中癌幕,同時(shí)將block和group聯(lián)系起來衙耕。
dispatch_group_notify(dispatch_group_t group, <#dispatch_queue_t queue#>, <#^(void)block#>)
是等到之前與group聯(lián)系過的block執(zhí)行完后會(huì)把這個(gè)block提交到queue中。
無論向什么樣的Dispatch Queue中追加任務(wù)序芦,使用Dispatch Group都可以監(jiān)視這些任務(wù)的結(jié)束臭杰。
另外,在Dispatch Group中也可以使用dispatch_group_wait函數(shù)僅等待全部任務(wù)執(zhí)行結(jié)束谚中。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block3");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_wait
函數(shù)會(huì)同步地等待與group有聯(lián)系的block執(zhí)行完成渴杆。
這個(gè)函數(shù)的第二個(gè)參數(shù)是等待時(shí)間,上面的示例中是永久等待宪塔,如果想要指定等待時(shí)間:
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
//與group聯(lián)系的任務(wù)已全部執(zhí)行完
}else{
//雖然過了指定時(shí)間磁奖,但還沒執(zhí)行完
}
Dispatch_barrier_async
在訪問數(shù)據(jù)庫或文件時(shí),如果多個(gè)讀取處理并行操作是不會(huì)有問題的某筐,但是寫入處理不可以與其他的寫入處理以及包含讀取處理的其他處理并行執(zhí)行比搭。也就是說,為了高效訪問南誊,讀取處理追加到Concurrent Dispatch Queue中身诺,寫入處理在任意一個(gè)讀取處理沒有執(zhí)行的狀態(tài)下,追加到Serial Dispatch Queue中即可(在寫入處理沒有執(zhí)行之前霉赡,讀取處理不可執(zhí)行)。
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//讀取處理block1
});
dispatch_async(queue, ^{
//讀取處理block2
});
dispatch_async(queue, ^{
//讀取處理block3
});
dispatch_async(queue, ^{
//寫入處理block4
});
dispatch_async(queue, ^{
//讀取處理block5
});
如果像上面這樣簡(jiǎn)單地在dispatch_asycn
函數(shù)中加入寫入處理幔托,那么根據(jù)Concurrent Dispatch的性質(zhì)穴亏,就有可能先執(zhí)行了block5的寫入處理蜂挪,然后在寫入處理前面的讀取處理中就會(huì)讀取到與期待不符的數(shù)據(jù),還可能因非法訪問導(dǎo)致應(yīng)用程序異常結(jié)束嗓化,如果追加多個(gè)寫入處理棠涮,則可能發(fā)生更多問題,比如數(shù)據(jù)競(jìng)爭(zhēng)刺覆。
因此我們要使用dispatch_barrier_async(<#dispatch_queue_t queue#>, <#^(void)block#>)
函數(shù)严肪。dispatch_barrier_asycn
,調(diào)用這個(gè)函數(shù)會(huì)把blcok提交到queue里并且立即返回隅津,不會(huì)等待block執(zhí)行诬垂。blcok被添加到queue后,它會(huì)等待之前添加的blcok執(zhí)行完后才開始執(zhí)行伦仍,并且在它之后添加的block會(huì)等待它自己執(zhí)行完后才會(huì)執(zhí)行结窘。
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//讀取處理block1
});
dispatch_async(queue, ^{
//讀取處理block2
});
dispatch_async(queue, ^{
//讀取處理block3
});
dispatch_barrier_async(queue, ^{
//寫入處理block4,等待前三個(gè)block執(zhí)行完后才會(huì)執(zhí)行這一個(gè)
});
dispatch_async(queue, ^{
//讀取處理block5充蓝,等待block4執(zhí)行完后才會(huì)執(zhí)行這一個(gè)
});
dispath_after
想要在指定時(shí)間后將block添加到隊(duì)列里隧枫,可以使用dispatch_after函數(shù)來實(shí)現(xiàn)。
dispatch_time_t time = dispatch_time( DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"wait 3 seconds");
});
需要注意的是谓苟,dispatch_after函數(shù)并不是在指定的時(shí)間后執(zhí)行block官脓,而是在指定的時(shí)間將block添加到queue中。在上面的代碼中涝焙,因?yàn)橹骶€程在RunLoop中執(zhí)行卑笨,所以在每隔1/60秒執(zhí)行的RunLoop中,block最快在3s后執(zhí)行仑撞,最慢在3s+1/60s后執(zhí)行赤兴,并且在主線程又大量處理追加或主線程的處理本身有延時(shí),這個(gè)時(shí)間會(huì)更長(zhǎng)隧哮。
dispatch_after的第一個(gè)參數(shù)是指定時(shí)間用的dispatch_time_t
類型的值桶良,該值可以用dispatch_time或dispatch_walltime函數(shù)獲得。
dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)
dispatch_time函數(shù)能夠獲取從第一個(gè)參數(shù)指定的時(shí)間開始沮翔,到第二個(gè)參數(shù)指定的單位時(shí)間之后的時(shí)間陨帆。第一個(gè)參數(shù)設(shè)置為DISPATCH_TIME_NOW
表示是從現(xiàn)在的時(shí)間開始的。第二個(gè)參數(shù)是數(shù)值和NSEC_TIME_NOW的乘積得到單位為毫微秒的數(shù)值采蚀。"ull"是C語言的數(shù)值字面量疲牵,是顯示表示類型時(shí)使用的字符串(標(biāo)識(shí)“unsinged long long”)。
dispatch_walltime函數(shù)由POSIX中使用的struct time spec類型的時(shí)間得到dispatch_time_t類型的值榆鼠。dispatch_time通常用于計(jì)算相對(duì)時(shí)間瑰步,dispatch_walltime通常用于計(jì)算絕對(duì)時(shí)間。例如在dispatch_after函數(shù)中想指定2016年11月11日11時(shí)11分11秒這一絕對(duì)時(shí)間的情況璧眠。
dispatch_walltime(const struct timespec *when, <#int64_t delta#>)
第一個(gè)參數(shù)是一個(gè)結(jié)構(gòu)體指針,我們可以通過NSDate對(duì)象操作完成:
dispatch_time_t getDispatchTimeByDate(NSDate * date){
NSTimeInterval interval;
double second, subsecond;
struct timespec timespec;
dispatch_time_t milestone;
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second);//分解interval,返回只是interval小數(shù)部分责静,second獲得整數(shù)部分
timespec.tv_nsec = subsecond * NSEC_PER_SEC;
milestone = dispatch_walltime(×pec, 0);
return milestone;
}
dispatch_apply
dispatch_apply(<#size_t iterations#>, dispatch_queue_t _Nonnull queue, <#^(size_t)block#>)
函數(shù)是dispatch_sycn
和Dispatch Group
的關(guān)聯(lián)API袁滥。該函數(shù)按指定的次數(shù)將指定的block追加到指定的Dispatch Group中,并等待所有的block執(zhí)行完后函數(shù)返回灾螃。第一個(gè)參數(shù)是迭代的次數(shù)题翻,第二個(gè)參數(shù)是指定的queue,第三個(gè)參數(shù)是指定的block腰鬼,block里有一個(gè)size_t類型的參數(shù)嵌赠,用來區(qū)分各個(gè)block使用。
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_apply(10, serialQueue, ^(size_t index) {
NSLog(@"serialQueue %zu, %@", index, [NSThread currentThread]);
});
NSLog(@"test1");
[2510:77935] serialQueue 0, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 1, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 2, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 3, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 4, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 5, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 6, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 7, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 8, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 9, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] test1
串行隊(duì)列里只有一個(gè)線程熄赡,他會(huì)按順序迭代執(zhí)行block姜挺,并等到所有的block執(zhí)行完后再執(zhí)行后面的代碼。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"queue %zu, %@", index, [NSThread currentThread]);
});
NSLog(@"test");
[2510:78206] queue 4, <NSThread: 0x60000007d2c0>{number = 8, name = (null)}
[2510:78208] queue 6, <NSThread: 0x61800007d380>{number = 10, name = (null)}
[2510:78205] queue 2, <NSThread: 0x608000076380>{number = 7, name = (null)}
[2510:78207] queue 5, <NSThread: 0x610000262c00>{number = 9, name = (null)}
[2510:78016] queue 0, <NSThread: 0x618000071b80>{number = 5, name = (null)}
[2510:77935] queue 3, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:78013] queue 1, <NSThread: 0x600000265e40>{number = 6, name = (null)}
[2510:78209] queue 7, <NSThread: 0x608000079cc0>{number = 11, name = (null)}
[2510:78206] queue 8, <NSThread: 0x60000007d2c0>{number = 8, name = (null)}
[2510:78208] queue 9, <NSThread: 0x61800007d380>{number = 10, name = (null)}
[2510:77935] test
在并發(fā)隊(duì)列里彼硫,會(huì)根據(jù)需要生成必要的線程一步地執(zhí)行block炊豪,dispatch_apply
依然要等到所有block執(zhí)行完后再執(zhí)行后面的代碼。
我們來看一下下面的代碼
NSArray * array = @[@"0", @"1", @"2", @"3",@"4", @"5"];
dispatch_apply([array count], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t index) {
NSLog(@"index = %zu: %@", index , array[index]);
});
這樣可以簡(jiǎn)單地在并發(fā)隊(duì)列里對(duì)所有元素執(zhí)行block拧篮,不必一個(gè)個(gè)編寫for循環(huán)词渤。
由于dispatch_apply
和dispatch_sycn
函數(shù)相同,會(huì)等待處理執(zhí)行結(jié)束串绩,因此推薦在dispatch_asycn
函數(shù)中非同步地執(zhí)行dispatch_apply
函數(shù)缺虐。
NSArray * array = @[@"aa", @"bb", @"cc", @"dd",@"ee", @"rr"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^{
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"index == %zu: %@", index, [array objectAtIndex:index]);
});
//等到dispatch_apply函數(shù)中的處理全部結(jié)束
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"done");
});
});
Dispatch Semaphore
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray * array = @[].mutableCopy;
for (int i = 0; i < 100000; i++) {
dispatch_async(queue, ^{
[array addObject:[NSNumber numberWithInt:i]];
});
}
由于該代碼使用Global Dispatch Queue 更新NSMutableArray對(duì)象,由于多個(gè)線程資源搶奪礁凡,所以執(zhí)行后由于內(nèi)存錯(cuò)誤導(dǎo)致應(yīng)用程序異常結(jié)束的概率非常高高氮。此時(shí)應(yīng)使用Dispatch Semaphore,它可以被當(dāng)做鎖來使用把篓,YYCache中的就使用了dispatch_semaphore
處理磁盤緩存的線程安全參考纫溃。
Dispatch semaphore是持有計(jì)數(shù)的信號(hào),該計(jì)數(shù)是多線程編程中的計(jì)數(shù)類型信號(hào)韧掩。所謂信號(hào)紊浩,類似于過馬路時(shí)常用的手旗,可以通過時(shí)舉起手旗疗锐,不可通過時(shí)放下手旗坊谁。而在Dispatch semaphore,使用計(jì)數(shù)來實(shí)現(xiàn)該功能滑臊。計(jì)數(shù)為0時(shí)等待口芍,計(jì)數(shù)為1或大于1時(shí),減去1而不等待雇卷。
通過dispatch_semaphore_create(<#long value#>)
函數(shù)生成Dispatch Semaphore鬓椭。參數(shù)表示計(jì)數(shù)的初始值颠猴,
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait函數(shù)等待Dispatch Semaphore的計(jì)數(shù)值達(dá)到大于或等于1。當(dāng)計(jì)數(shù)值大于等于1小染,或者在等待中計(jì)數(shù)值大于等于1時(shí)翘瓮,對(duì)該計(jì)數(shù)進(jìn)行減法并從dispatch_semaphore_wait函數(shù)返回,第二個(gè)參數(shù)是由dispatch_time_t類型值指定等待時(shí)間。上面代碼表示永久等待。
dispatch_semaphore_wait函數(shù)返回為0時(shí)羽峰,可安全地執(zhí)行需要進(jìn)行排他控制的處理,該處理結(jié)束時(shí)通過dispatch_semaphore_signal函數(shù)將Dispatch Semaphore的計(jì)數(shù)值加1.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*
Dispatch Semaphore的計(jì)數(shù)初始值設(shè)為1呵扛,保證訪問array的線程同時(shí)只有一個(gè)。
*/
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray * array = @[].mutableCopy;
for (int i = 0; i < 10000; i++) {
dispatch_async(queue, ^{
/*
當(dāng)semaphore的計(jì)數(shù)值大于或等于1時(shí)筐带,會(huì)將semaphore的計(jì)數(shù)值減去1今穿,dispatch_semaphore_wait函數(shù)會(huì)執(zhí)行返回,即函數(shù)返回后semaphore的計(jì)數(shù)值為0烫堤,當(dāng)其他線程執(zhí)行block中的代碼時(shí)荣赶,會(huì)因?yàn)閟emaphore的計(jì)數(shù)值為0而處于等待狀態(tài),即訪問array的線程只有一個(gè)鸽斟,因此可以安全的進(jìn)行更新 */
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[NSNumber numberWithInt:i]];
/*
將semaphore的計(jì)數(shù)值加1拔创,如果有通過dispatch_sycn_wait函數(shù)等待semaphore計(jì)數(shù)值增加的線程,就由最先等待的線程執(zhí)行*/
dispatch_semaphore_signal(semaphore);
});
}
使用信號(hào)量實(shí)現(xiàn)URLSession同步
NSUrlSession的方法全是異步的富蓄,要想實(shí)現(xiàn)同步的剩燥,也可以使用信號(hào)量來實(shí)現(xiàn)。
NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
NSURLSessionDataTask * data = [session dataTaskWithURL:[NSURL URLWithString:@"http://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"222222");
dispatch_semaphore_signal(sem);
}];
[data resume];
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"333333");
由信號(hào)量于初始值是0立倍,調(diào)用dispatch_semaphore_wait后信號(hào)量值會(huì)小于0灭红,這時(shí)候dispatch_semaphore_wait不會(huì)立即返回,它會(huì)等待dispatch_semaphore_signal后才會(huì)返回口注。因此會(huì)先打印222222变擒,之后才會(huì)打印333333
使用信號(hào)量控制并行隊(duì)列的線程數(shù)
使用信號(hào)量控制并行隊(duì)列的線程數(shù)
dispatch_suspend / dispatch_resume
當(dāng)追加大量處理到Dispatch Queue 時(shí),在追加處理的過程中寝志,有時(shí)希望不執(zhí)行已追加的處理娇斑。例如鹽酸結(jié)果被block截獲時(shí),一些處理會(huì)對(duì)這個(gè)演算結(jié)果造成影響材部。
在這種情況下毫缆,只要掛起Dispatch Queue即可,當(dāng)可執(zhí)行的時(shí)候再恢復(fù)乐导。
dispatch_suspend函數(shù)掛起指定的Dispatch Queue苦丁。
dispatch_suspend(queue);
dispatch_resume函數(shù)恢復(fù)指定的Dispatch Queue。
dispatch_resume (queue);
這些函數(shù)對(duì)已經(jīng)執(zhí)行的處理沒有影響物臂,掛起后旺拉,追加到Dispatch Queue中但還沒有執(zhí)行的處理在暫停執(zhí)行产上,而恢復(fù)則會(huì)使這些處理能夠繼續(xù)執(zhí)行。
dispatch_once
dispatch_once函數(shù)是保證在應(yīng)用程序中只執(zhí)行一次指定處理的API蛾狗。
+ (CustomModel *)shareInstance{
static CustomModel * shateInstance = nil;
static dispatch_once_t onceToken;
NSLog(@"first == %ld",onceToken);
dispatch_once(&onceToken, ^{
shateInstance = [[CustomModel alloc] init];
});
NSLog(@"second == %ld",onceToken);
NSLog(@"-----------------");
return shateInstance;
}
使用dispatch_once函數(shù)蒂秘,該源代碼即使在多線程環(huán)境下執(zhí)行,也可保證百分百安全淘太。
dispatch_once函數(shù)的原型是void _dispatch_once(dispatch_once_t *predicate, DISPATCH_NOESCAPE dispatch_block_t block)
,這個(gè)函數(shù)的作用是使得block在整個(gè)程序的生命周期中只執(zhí)行一次,每次調(diào)用這塊代碼時(shí)通過predicate來檢查规丽,在這里predicate必須嚴(yán)格的初始化為0蒲牧。
可以測(cè)試線面的輸出:
[CustomModel shareInstance];
[CustomModel shareInstance];
[CustomModel shareInstance];
[19923:1187470] first == 0
[19923:1187470] second == -1
[19923:1187470] -----------------
[19923:1187470] first == -1
[19923:1187470] second == -1
[19923:1187470] -----------------
[19923:1187470] first == -1
[19923:1187470] second == -1
[19923:1187470] -----------------
若onceToken被初始化為0,那么在調(diào)用dispatch_once函數(shù)時(shí)檢測(cè)到其值為0赌莺,就執(zhí)行block冰抢,執(zhí)行完后onceToken減一。下次調(diào)用dispatch_once函數(shù)時(shí)檢測(cè)到onceToken = -1艘狭,將不會(huì)執(zhí)行block挎扰。
Dispatch Sources
iOS 中有兩種 Source,一種是 Run Loop Source ,一種是 Dispatch Source巢音。
Source 可以理解為產(chǎn)生事件的地方遵倦,Source 產(chǎn)生事件,然后 Source 的回調(diào)函數(shù)負(fù)責(zé) 處理這些事件官撼。
Dispatch Source 也會(huì)產(chǎn)生一些特定的事件梧躺,當(dāng)這些事件發(fā)生的時(shí)候,其回調(diào)的 block 會(huì)自動(dòng)加入到 對(duì)應(yīng)的 dispatch queue 中傲绣。
使用dispatch_source_create
創(chuàng)建的源默認(rèn)是懸停(suspended)的掠哥,必須要使用dispatch_resume
來開啟事件的傳遞。
Dispatch Sources的類型:
- DISPATCH_SOURCE_TYPE_DATA_ADD 用戶自定義的事件-變量相加
- DISPATCH_SOURCE_TYPE_DATA_OR 用戶自定義的事件-變量相或
- DISPATCH_SOURCE_TYPE_MACH_RECV Mach 端口接收事件
- DISPATCH_SOURCE_TYPE_MACH_SEND Mach 端口發(fā)送事件
- DISPATCH_SOURCE_TYPE_PROC 與進(jìn)程相關(guān)的事件
- DISPATCH_SOURCE_TYPE_READ 文件可讀
- DISPATCH_SOURCE_TYPE_SIGNAL 接收到 UNIX 信號(hào)
- DISPATCH_SOURCE_TYPE_TIMER 定時(shí)器
- DISPATCH_SOURCE_TYPE_VNODE 文件系統(tǒng)有變更
- DISPATCH_SOURCE_TYPE_WRITE 文件可寫
- DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
這些事件都是來自于 XNU 內(nèi)核中秃诵,kqueue 是用來處理這些事件的最好的一種方法续搀,Dispatch Source 就是對(duì) kqueue 的封裝。
使用的 Dispatch Source 而不使用 dispatch_async 的唯一原因就是利用聯(lián)結(jié)的優(yōu)勢(shì)菠净。
聯(lián)結(jié)的大致流程:在任一線程上調(diào)用函數(shù) dispatch_source_merge_data
后禁舷,會(huì)把Dispatch Source回調(diào)的block提交到關(guān)聯(lián)的隊(duì)列中。dispatch_source_merge_data
這個(gè)函數(shù)有一個(gè)參數(shù)是unsigned long value
嗤练,當(dāng)Dispatch Source的類型是DISPATCH_SOURCE_TYPE_DATA_ADD
榛了,事件在連接會(huì)把這些數(shù)字相加;當(dāng)Dispatch Source的類型是DISPATCH_SOURCE_TYPE_DATA_OR
時(shí)煞抬,事件在連接會(huì)把這些數(shù)字邏輯與運(yùn)算霜大。當(dāng)block執(zhí)行時(shí),我們可以使用dispatch_source_get_data
函數(shù)訪問當(dāng)前值革答,然后這個(gè)值會(huì)被重置為0战坤。
假設(shè)一些異步執(zhí)行的代碼會(huì)更新一個(gè)進(jìn)度條曙强,我們可以將UI更新工作push到主線程中。然而途茫,這些事件可能會(huì)有一大堆碟嘴,我們不想對(duì)UI進(jìn)行頻繁而累贅的更新,理想的情況是當(dāng)主線程繁忙時(shí)將所有的改變聯(lián)結(jié)起來囊卜。
用dispatch source就完美了娜扇,使用DISPATCH_SOURCE_TYPE_DATA_ADD
,我們可以將工作拼接起來栅组,然后主線程可以知道從上一次處理完事件到現(xiàn)在一共發(fā)生了多少改變雀瓢,然后將這一整段改變一次更新至進(jìn)度條。
// 指定源的類型DISPATCH_SOURCE_TYPE_DATA_ADD玉掸。設(shè)定Main Dispatch Queue 為追加處理的Dispatch Queue
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
__block NSUInteger totalComplete = 0;
dispatch_source_set_event_handler(_source, ^{
//當(dāng)處理事件被最終執(zhí)行時(shí)刃麸,計(jì)算后的數(shù)據(jù)可以通過dispatch_source_get_data來獲取。這個(gè)數(shù)據(jù)的值在每次dispatch_source_get_data會(huì)被重置司浪,所以totalComplete的值是最終累積的值泊业。
NSUInteger value = dispatch_source_get_data(_source);
totalComplete += value;
NSLog(@"進(jìn)度:%@", @((CGFloat)totalComplete/100));
});
//恢復(fù)源
dispatch_resume(_source);
//恢復(fù)源后,就可以通過dispatch_source_merge_data向Dispatch Source(分派源)發(fā)送事件:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
for (NSUInteger index = 0; index < 100; index++) {
dispatch_source_merge_data(_source, 1);
usleep(20000);//0.02秒
}
});
從log可以看出啊易,dispatch_source_merge_data
調(diào)用了100次吁伺,但是變量event handler block并沒有執(zhí)行100次, 這是因?yàn)閷?duì)應(yīng)的隊(duì)列在接收到 Source 事件之后认罩,假如隊(duì)列處于空閑狀態(tài)箱蝠,就會(huì)執(zhí)行對(duì)應(yīng)的回調(diào) Block,假如隊(duì)列處于 busy 狀態(tài)垦垂,該事件就會(huì)和后面的一系列同種事件通過一定的方式被合并起來(也就是把unsigned long value加在一起)宦搬,等到隊(duì)列空閑的時(shí)候再執(zhí)行block。
Dispatch Source Timer
利用 Dispatch Source 的 DISPATCH_SOURCE_TYPE_TIMER
類型劫拗,我們可以創(chuàng)建一個(gè) 跨線程的 定時(shí)器(我們平時(shí)使用的 NSTimer 是基于 Run Loop 的 timer 事件间校,只能在對(duì)應(yīng)的線程里觸發(fā))
dispatch_queue_t queue = dispatch_get_main_queue();
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);//創(chuàng)建一個(gè) timer;
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);//配置 timer页慷,從現(xiàn)在起憔足,每?jī)擅朐谥骶€程觸發(fā)一次,精度為0s
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"%ld", self.count++);
});//timer 觸發(fā)之后的回調(diào) block
dispatch_resume(self.time); //啟動(dòng) timer
有一個(gè)使用Dispatch Source Timer來解決輸入框頻繁輸入字符的問題的GCDThrottle酒繁。
GCDThrottle有兩種類型:
typedef NS_ENUM(NSInteger, GCDThrottleType) {
GCDThrottleTypeDelayAndInvoke,/**< Throttle will wait for [threshold] seconds to invoke the block, when new block comes, it cancels the previous block and restart waiting for [threshold] seconds to invoke the new one. */
GCDThrottleTypeInvokeAndIgnore,/**< Throttle invokes the block at once and then wait for [threshold] seconds before it can invoke another new block, all block invocations during waiting time will be ignored. When [threshold] seconds passed by, it invokes the last ignored block (if have). */
};
GCDThrottleTypeDelayAndInvoke
對(duì)應(yīng)的代碼:
NSMutableDictionary *scheduledSources = self.scheduledSourcesForMode0;
dispatch_source_t source = scheduledSources[key];
if (source) {
dispatch_source_cancel(source);
}
source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, threshold * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
dispatch_source_set_event_handler(source, ^{
block();
dispatch_source_cancel(source);
[scheduledSources removeObjectForKey:key];
});
dispatch_resume(source);
scheduledSources[key] = source;
從代碼可以看出滓彰,block會(huì)在延時(shí)threshold
時(shí)間后執(zhí)行,如果在此期間重新調(diào)用了這個(gè)方法州袒,會(huì)把之前的source取消掉揭绑,這樣之前的block也不會(huì)執(zhí)行。新的block會(huì)重新在threshold
時(shí)間后執(zhí)行。
GCDThrottleTypeInvokeAndIgnore
對(duì)應(yīng)的代碼:
NSMutableDictionary *scheduledSources = self.scheduledSourcesForMode1;
NSMutableDictionary *blocksToInvoke = self.blocksToInvoke;
blocksToInvoke[key] = [block copy];
dispatch_source_t source = scheduledSources[key];
if (source) { return; }
source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, 0), threshold * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(source, ^{
GCDThrottleBlock blockToInvoke = blocksToInvoke[key];
if (blockToInvoke) {
blockToInvoke();
[blocksToInvoke removeObjectForKey:key];
} else {
dispatch_source_cancel(source);
[scheduledSources removeObjectForKey:key];
}
});
dispatch_resume(source);
scheduledSources[key] = source;
從代碼可以看出他匪,由于使用了DISPATCH_TIME_NOW菇存,當(dāng)source resume后,block會(huì)立即執(zhí)行邦蜜,而source并沒有被取消依鸥,之后再調(diào)用這個(gè)方法時(shí)都會(huì)直接返回:
(if (source) { return; })
等到設(shè)置的threshold時(shí)間到后才會(huì)取消源
dispatch_source_cancel(source);
[scheduledSources removeObjectForKey:key];