iOS-多線程之GCD

iOS實現(xiàn)多線程編程有四種方式Pthreads,NSThread,NSOperation & NSOperationQueue和GCD.GCD(Grand Central Dispatch)是異步執(zhí)行任務(wù)的技術(shù)之一,一般將應(yīng)用程序中線程管理代碼在系統(tǒng)級實現(xiàn).開發(fā)過程中只需要定義想執(zhí)行的任務(wù)并追加到適當(dāng)?shù)腄ispatch Queue,GCD就能生成必要的線程執(zhí)行任務(wù).

GCD以其難以置信的API風(fēng)格,實現(xiàn)了極其復(fù)雜繁瑣的多線程編程,是多線程編程中必不可少一把利劍.GCD屬于系統(tǒng)級的線程管理,GCD中的FIFO隊列稱為dispatch queue,用來保證先進來的任務(wù)先得到執(zhí)行.

基礎(chǔ)概念

進程是一個正在執(zhí)行程序的實例,在iOS中可以理解為一個正在運行App,每個進程都有一個單獨的Process ID(進程ID).現(xiàn)在操作系統(tǒng)將線程作為基本的操作單元,線程是一組寄存器的狀態(tài),一個進程包含了多個線程.一個進程內(nèi)的所有線程都共享虛擬內(nèi)存空間,文件描述符和各種句柄.單核CPU每次執(zhí)行的CPU命令數(shù)為1,那么單核CPU是如何做到同時執(zhí)行多條命令呢?

OS X和iOS的核心XNU內(nèi)存在發(fā)生操作系統(tǒng)事件時(如每隔一定時間,喚起系統(tǒng)調(diào)用等)會切換執(zhí)行路徑.執(zhí)行路徑中的狀態(tài),例如CPU的寄存器狀態(tài)等信息會保存到各自專用的內(nèi)存塊中,從切換目標路徑到專用的內(nèi)存塊中,復(fù)原CPU寄存器信息,繼續(xù)執(zhí)行切換路徑的CPU命令列.稱之為"上下文切換".

由于使用多線程的程序可以在在某個線程和其他線程之間反復(fù)多次進行上下文切換,因此單核CPU看起來像并列執(zhí)行多個線程一樣,如果是多核CPU,是真正的提供了多個CPU的執(zhí)行多個線程的技術(shù).

多線程編程中最容易出現(xiàn)的問題是多個線程更新相同的資源導(dǎo)致數(shù)據(jù)不一致(數(shù)據(jù)競爭),停止等待事件會導(dǎo)致多個線程相互持續(xù)等待(死鎖),使用太多的線程會消耗大量的內(nèi)存.多線程能夠保證應(yīng)用程序的響應(yīng)性能.

基礎(chǔ)知識

GCD編程中只需要將執(zhí)行的任務(wù)加入Dispatch Queue中即可,Dispatch Queue按照添加的順序FIFO(First-In-First-Out)先入先出的規(guī)則執(zhí)行處理.Dispatch Queue有兩種類型串行隊列(Serial Dispatch Queue)和并行隊列(Concurrent Dispatch Queue) .

串行隊列使用一個線程執(zhí)行任務(wù),等待上一個任務(wù)執(zhí)行完成之后執(zhí)行下一個任務(wù).并行隊列會使用多個線程執(zhí)行任務(wù),不需要等待之前的任務(wù)是否完成.iOS 和 OS X的核心-XNU內(nèi)核決定應(yīng)當(dāng)使用的線程數(shù),生成需要的線程來執(zhí)行處理,當(dāng)處理結(jié)束,應(yīng)當(dāng)執(zhí)行的處理數(shù)減少時,XNU內(nèi)核會結(jié)束不在需要的線程.

GCD中dispatch_get_main_queue獲得主線程中執(zhí)行的隊列,因為主線程只有一個,所以主隊列是串行列.

dispatch_get_global_queue是所有應(yīng)用程序都能創(chuàng)建的并發(fā)隊列,不需要手動創(chuàng)建Dispatch隊列.

GCD 有兩種派發(fā)方式:同步派發(fā)和異步派發(fā),這里的同步和異步指的是 “任務(wù)派發(fā)方式”,而非任務(wù)的執(zhí)行方式.

dispatch_sync定義如下:

/*!
 * @function dispatch_sync
 *
 * @abstract
 * Submits a block for synchronous execution on a dispatch queue.
 *
 * @discussion
 * Submits a block to a dispatch queue like dispatch_async(), however
 * dispatch_sync() will not return until the block has finished.
 *
 * Calls to dispatch_sync() targeting the current queue will result
 * in dead-lock. Use of dispatch_sync() is also subject to the same
 * multi-party dead-lock problems that may result from the use of a mutex.
 * Use of dispatch_async() is preferred.
 *
 * Unlike dispatch_async(), no retain is performed on the target queue. Because
 * calls to this function are synchronous, the dispatch_sync() "borrows" the
 * reference of the caller.
 *
 * As an optimization, dispatch_sync() invokes the block on the current
 * thread when possible.
 *
 * @param queue
 * The target dispatch queue to which the block is submitted.
 * The result of passing NULL in this parameter is undefined.
 *
 * @param block
 * The block to be invoked on the target dispatch queue.
 * The result of passing NULL in this parameter is undefined.
 */
#ifdef __BLOCKS__
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
#endif
  • 將block的中的任務(wù)同步提交給目標Queue執(zhí)行.
  • 阻塞當(dāng)前Queue,執(zhí)行block任務(wù),回調(diào)當(dāng)前Queue.

這就很容易解釋設(shè)置dispatch_sync的隊列為當(dāng)前Queue發(fā)生死鎖的原因,調(diào)用dispatch_sync會阻塞主隊列,block無法執(zhí)行,不會回調(diào)主隊列.下面的代碼是無法運行的:

    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"FlyElephant---執(zhí)行了");
    });

dispatch_async與dispatch_sync最大不同的是不會阻塞當(dāng)前隊列:

  • 將block的中的任務(wù)同步提交給目標Queue執(zhí)行.
  • 不阻塞當(dāng)前Queue,執(zhí)行block任務(wù),回調(diào)當(dāng)前Queue.
dispatch_async(dispatch_get_main_queue(), ^{
        for (NSInteger i=0; i < 20; i++) {
            NSLog(@"FlyLElephant---異步:%ld",i);
        }
    });
    
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"同步數(shù)據(jù)");
    });

并行與串行(主線程是串行隊列,dispatch_get_global_queue獲取對全局隊列是并行隊列):

dispatch_queue_t serialQueue = dispatch_queue_create("com.flyelephant.wwww", DISPATCH_QUEUE_SERIAL);
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.flyelephant.www", DISPATCH_QUEUE_CONCURRENT);

全局隊列有四個優(yōu)先級High,Default,Low和Background:

#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

串行和并行執(zhí)行順序:

    dispatch_queue_t serialQueue = dispatch_queue_create("com.flyelephant.wwww", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(serialQueue, ^{
        NSLog(@"Serial--1");
    });
    
    dispatch_async(serialQueue, ^{
        NSLog(@"Serial--2");
    });
    
    dispatch_async(serialQueue, ^{
        NSLog(@"Serial--3");
    });
    
    dispatch_async(serialQueue, ^{
        NSLog(@"Serial--4");
    });
    
    
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.flyelephant.www", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"Concurrent--1");
    });
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"Concurrent--2");
    });
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"Concurrent--3");
    });
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"Concurrent--4");
    });
FlyElephant.png

dispatch_set_target_queue

dispatch_set_target_queue除了能用來設(shè)置隊列的優(yōu)先級之外,還能夠創(chuàng)建隊列的層次體系,將不同異步任務(wù)設(shè)置為目標隊列.

    dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);//目標隊列
    dispatch_queue_t serialQueue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行隊列
    dispatch_queue_t conQueue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊列

    dispatch_set_target_queue(serialQueue, targetQueue);
    dispatch_set_target_queue(conQueue, targetQueue);
    
    dispatch_async(serialQueue, ^{
        NSLog(@"FlyElephant---任務(wù)1 開始");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"FlyElephant---任務(wù)1 結(jié)束");
    });
    
    dispatch_async(conQueue, ^{
        NSLog(@"FlyElephant---任務(wù)2 開始");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"FlyElephant---任務(wù)2 結(jié)束");
    });
    
    dispatch_async(conQueue, ^{
        NSLog(@"FlyElephant---任務(wù)3 開始");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"FlyElephant---任務(wù)3 結(jié)束");
    });
FlyElephant.png

dispatch_after延遲執(zhí)行

dispatch_after只是延時提交block,不是延時立刻執(zhí)行.

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"延遲執(zhí)行");
    });

dispatch_barrier_async

dispatch_barrier_async函數(shù)類似于多線程中國柵欄的作用,它等待所有位于barrier函數(shù)之前的操作執(zhí)行完畢后執(zhí)行,并且在barrier函數(shù)執(zhí)行之后,barrier函數(shù)之后的操作才會得到執(zhí)行,該函數(shù)需要同dispatch_queue_create函數(shù)生成的concurrent Dispatch Queue隊列一起使用.

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.barrier.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"FlyElephant-1");
    });
    
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"FlyElephant-2");
    });
    
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"FlyElephant-3");
    });
    
    dispatch_barrier_async(concurrentQueue, ^(){
        NSLog(@"FlyElephant---dispatch_barrier_async");
    });
    
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"FlyElephant-4");
    });
    
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"FlyElephant-5");
    });
    
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"FlyElephant-6");
    });
FlyElephant.png

dispatch_apply

dispatch_apply類似一個for循環(huán),會在指定的dispatch queue中運行block任務(wù)n次,如果隊列是并發(fā)隊列,則會并發(fā)執(zhí)行block任務(wù),dispatch_apply是一個同步調(diào)用,block任務(wù)執(zhí)行n次后才返回.dispatch_apply會將Block任務(wù)執(zhí)行完成之后才會繼續(xù)往下執(zhí)行.

dispatch_queue_t queue = dispatch_queue_create("com.flyelephant.www", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(10, queue, ^(size_t i) {
        NSLog(@"并行執(zhí)行---%ld",i);
    });
    NSLog(@"FlyElephant---執(zhí)行完成");
    
    
    dispatch_queue_t serialQueue = dispatch_queue_create("com.flyelephant.www", DISPATCH_QUEUE_SERIAL);
    dispatch_apply(10, serialQueue, ^(size_t i) {
        NSLog(@"串行執(zhí)行---%ld",i);
    });
    NSLog(@"FlyElephant---執(zhí)行完成");
FlyElephant.png

dispatch_once

dispatch_once只執(zhí)行一次,單例實現(xiàn)中使用dispatch_once比較常見.

static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
    });

dispatch_group_t

dispatch_group_t創(chuàng)建一個隊列組,dispatch_group_wait表示等待group設(shè)置的時間,dispatch_group_notify是在所有的queue任務(wù)完成之后執(zhí)行notify里面的代碼.

dispatch_queue_t dispatchQueue =  dispatch_queue_create("com.group.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t dispatchGroup = dispatch_group_create();
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^{
        NSLog(@"FlyElephant---任務(wù)1完成");
    });
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^{
        sleep(3);
        NSLog(@"FlyElephant---任務(wù)2完成");
    });
    
    dispatch_group_notify(dispatchGroup, dispatchQueue, ^{
        NSLog(@"dispatch_group_notify 執(zhí)行");
    });
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^{
        dispatch_group_wait(dispatchGroup, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
        NSLog(@"dispatch_group_wait 結(jié)束");
    });
FlyElephant.png

可以將wait的時間設(shè)置一直等待,dispatch_group_wait會阻塞當(dāng)前線程(所以不能放在主線程調(diào)用)一直等待,當(dāng)group上任務(wù)完成,或者等待時間超過設(shè)置的超時時間會結(jié)束等待.

dispatch_queue_t dispatchQueue =  dispatch_queue_create("com.group.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t dispatchGroup = dispatch_group_create();
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^{
        NSLog(@"FlyElephant---任務(wù)1完成");
    });
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^{
        sleep(3);
        NSLog(@"FlyElephant---任務(wù)2完成");
    });
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^{
        NSLog(@"FlyElephant---任務(wù)3完成");
    });
    
    dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER);
    NSLog(@"dispatch_group_wait 結(jié)束");


    dispatch_group_async(dispatchGroup, dispatchQueue, ^{;
        NSLog(@"FlyElephant---任務(wù)4完成");
    });
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^{
        NSLog(@"FlyElephant---任務(wù)5完成");
    });
FlyElephant.png

dispatch_group_enter與dispatch_group_leave表示加入任務(wù)組和離開任務(wù)組,兩者要同時出現(xiàn),不然會出現(xiàn)崩潰.

dispatch_queue_t dispatchQueue =  dispatch_queue_create("com.group.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t dispatchGroup = dispatch_group_create();
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_enter(dispatchGroup);
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
        dispatch_async(globalQueue, ^{
            sleep(2);
            NSLog(@"FlyElephant---任務(wù)1完成");
            dispatch_group_leave(dispatchGroup);
        });
    });
    
    dispatch_group_enter(dispatchGroup);
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
        dispatch_async(globalQueue, ^{
            sleep(1);
            NSLog(@"FlyElephant---任務(wù)2完成");
            dispatch_group_leave(dispatchGroup);
        });
    });
    
    dispatch_group_enter(dispatchGroup);
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
        dispatch_async(globalQueue, ^{
            NSLog(@"FlyElephant---任務(wù)3完成");
            dispatch_group_leave(dispatchGroup);
        });
    });
    
    dispatch_group_notify(dispatchGroup, dispatchQueue, ^{
        NSLog(@"FlyElephant---dispatch_group_notify任務(wù)完成");
    });

所有的任務(wù)執(zhí)行完成之后執(zhí)行通知:


FlyElephant.png

如果不加入enter和leave,看下效果:

dispatch_queue_t dispatchQueue = dispatch_queue_create("com.group.www", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    dispatch_group_t dispatchGroup = dispatch_group_create();
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
        dispatch_async(globalQueue, ^{
            sleep(2);
            NSLog(@"FlyElephant---任務(wù)1完成");
        });
    });
    
    dispatch_group_async(dispatchGroup, dispatchQueue, ^(){
        dispatch_async(globalQueue, ^{
            sleep(3);
            NSLog(@"FlyElephant---任務(wù)2完成");
        });
    });
    
    dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^(){
       NSLog(@"FlyElephant---dispatch_group_notify");
    });
FlyElephant.png

dispatch_semaphore_t

dispatch_semaphore_t是一種保護線程同步的機制,使用dispatch_semaphore_signal加1,dispatch_semaphore_wait減1,為0的時候等待的設(shè)置方式來達到線程同步的目的和同步鎖一樣能夠解決資源搶占的問題.

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    
    self.data  = [[NSMutableArray alloc] init];
    
    for (NSInteger i = 0; i < 10; i++) {
        
        dispatch_async(queue, ^(){
            
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            
            [self.data addObject:[NSString stringWithFormat:@"FlyElephant---%ld",i]];
            
            dispatch_semaphore_signal(semaphore);
            
        });
    }
FlyElephant.png

參考資料:
官方文檔
線程到底是什么?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評論 3 425
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,635評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,539評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,255評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,646評論 1 326
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,838評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,146評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,338評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,565評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,983評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,257評論 1 292
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,059評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,296評論 2 376

推薦閱讀更多精彩內(nèi)容

  • 1. GCD簡介 什么是GCD呢?我們先來看看百度百科的解釋簡單了解下概念 引自百度百科:Grand Centra...
    千尋_544f閱讀 395評論 0 0
  • 多線程 在iOS開發(fā)中為提高程序的運行效率會將比較耗時的操作放在子線程中執(zhí)行,iOS系統(tǒng)進程默認啟動一個主線程,用...
    郭豪豪閱讀 2,609評論 0 4
  • GCD (Grand Central Dispatch) :iOS4 開始引入,使用更加方便,程序員只需要將任務(wù)添...
    池鵬程閱讀 1,346評論 0 2
  • iOS多線程之GCD 什么是GCD GCD(grand central dispatch) 是 libdispat...
    comst閱讀 1,216評論 0 0
  • 原創(chuàng)文章 轉(zhuǎn)載請注明出處, 謝謝! (~ o ~)Y 本文思維導(dǎo)圖 GCD是什么 全稱是 Grand Centra...
    Jimmy_P閱讀 4,699評論 10 67