關于GCD開發的一些事兒


在之前我們介紹過NSOperation的一些東西,這次我們來聊一聊另一個iOS開發最經常使用的技術之一 --- GCD,GCD將線程的管理移到系統級別,你只需要定義好要執行的任務,然后丟到合適的Dispatch queue,GCD會負責創建線程來執行你的代碼,由于這部分是處于系統級別,所以執行的性能通常非常高。GCD這部分代碼蘋果已開源,有興趣的可以去下載了解一下:地址
在介紹GCD之前我們先了解一下Quality of Service:

Quality of Service(QoS)

這是在iOS8之后提供的新功能,蘋果提供了幾個Quality of Service枚舉來使用:user interactive, user initiated, utility 和 background,通過這告訴系統我們在進行什么樣的工作,然后系統會通過合理的資源控制來最高效的執行任務代碼,其中主要涉及到CPU調度的優先級、IO優先級、任務運行在哪個線程以及運行的順序等等,我們通過一個抽象的Quality of Service參數來表明任務的意圖以及類別。

  • NSQualityOfServiceUserInteractive
    與用戶交互的任務,這些任務通常跟UI級別的刷新相關,比如動畫,這些任務需要在一瞬間完成
  • NSQualityOfServiceUserInitiated
    由用戶發起的并且需要立即得到結果的任務,比如滑動scroll view時去加載數據用于后續cell的顯示,這些任務通常跟后續的用戶交互相關,在幾秒或者更短的時間內完成
  • NSQualityOfServiceUtility
    一些可能需要花點時間的任務,這些任務不需要馬上返回結果,比如下載的任務,這些任務可能花費幾秒或者幾分鐘的時間
  • NSQualityOfServiceBackground
    這些任務對用戶不可見,比如后臺進行備份的操作,這些任務可能需要較長的時間,幾分鐘甚至幾個小時
  • NSQualityOfServiceDefault
    優先級介于user-initiated 和 utility,當沒有 QoS信息時默認使用,開發者不應該使用這個值來設置自己的任務

Qos可以跟GCD queue做個對照:


對照表

下面我們了解一下GCD的一些用法:

Dispatch Queue

開發者將需要執行的任務添加到合適的Dispatch Queue中即可,Dispatch Queue會根據任務添加的順序先到先執行,其中有以下幾種隊列:

  • main dispatch queue
    功能跟主線程一樣,通過dispatch_get_main_queue()來獲取,提交到main queue的任務實際上都是在主線程執行的,所以這是一個串行隊列
  • global dispatch queues
    系統給每個應用提供四個全局的并發隊列,這四個隊列分別有不同的優先級:高、默認、低以及后臺,用戶不能去創建全局隊列,只能根據優先級去獲取:
dispatch_queue_t queue ; 
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  • user create queue
    用戶可以通過dispatch_queue_create自己創建隊列,該函數有兩個參數,第一個是隊列的名稱,在debug的時候方便區分;第二個是隊列的一些屬性,NULL或者DISPATCH_QUEUE_SERIAL創建出來的隊列是串行隊列,如果傳遞DISPATCH_QUEUE_CONCURRENT則為并行隊列。
//創建并行隊列
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", DISPATCH_QUEUE_CONCURRENT);
  • 隊列優先級

dispatch_queue_create創建隊列的優先級跟global dispatch queue的默認優先級一樣,假如我們需要設置隊列的優先級,可以通過dispatch_queue_attr_make_with_qos_class或者dispatch_set_target_queue方法;

//指定隊列的QoS類別為QOS_CLASS_UTILITY
dispatch_queue_attr_t queue_attr = dispatch_queue_attr_make_with_qos_class (DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY,-1);
dispatch_queue_t queue = dispatch_queue_create("queue", queue_attr);

dispatch_set_target_queue的第一個參數為要設置優先級的queue,第二個參數是對應的優先級參照物

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.MyQueue",NULL);  
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);  
 
//serialQueue現在的優先級跟globalQueue的優先級一樣
dispatch_set_target_queue(serialQueue, globalQueue);  

  • dispatch_set_target_queue
    dispatch_set_target_queue除了能用來設置隊列的優先級之外,還能夠創建隊列的層次體系,當我們想讓不同隊列中的任務同步的執行時,我們可以創建一個串行隊列,然后將這些隊列的target指向新創建的隊列即可,比如
隊列體系.png
  dispatch_queue_t targetQueue = dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL);
  dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
  dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
  dispatch_set_target_queue(queue1, targetQueue);
  dispatch_set_target_queue(queue2, targetQueue);
  dispatch_async(queue1, ^{
        NSLog(@"do job1");
        [NSThread sleepForTimeInterval:3.f];
    });
  dispatch_async(queue2, ^{
        NSLog(@"do job2");
        [NSThread sleepForTimeInterval:2.f];
    });
  dispatch_async(queue2, ^{
        NSLog(@"do job3");
        [NSThread sleepForTimeInterval:1.f];
    });

可以看到執行的結果如下,這些隊列會同步的執行任務。

 GCDTests[13323:569147] do job1
 GCDTests[13323:569147] do job2
 GCDTests[13323:569147] do job3
  • dispatch_barrier_async
    dispatch_barrier_async用于等待前面的任務執行完畢后自己才執行,而它后面的任務需等待它完成之后才執行。一個典型的例子就是數據的讀寫,通常為了防止文件讀寫導致沖突,我們會創建一個串行的隊列,所有的文件操作都是通過這個隊列來執行,比如FMDB,這樣就可以避免讀寫沖突。不過其實這樣效率是有提升的空間的,當沒有更新數據時,讀操作其實是可以并行進行的,而寫操作需要串行的執行,如何實現呢:
dispatch_queue_t queue = dispatch_queue_create("Database_Queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"reading data1");
    });
    dispatch_async(queue, ^{
        NSLog(@"reading data2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"writing data1");
        [NSThread sleepForTimeInterval:1];
        
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"reading data3");
    });

執行結果如下:

GCDTests[13360:584316] reading data2
GCDTests[13360:584317] reading data1
GCDTests[13360:584317] writing data1
GCDTests[13360:584317] reading data3

我們將寫數據的操作放在dispatch_barrier_async中,這樣能確保在寫數據的時候會等待前面的讀操作完成,而后續的讀操作也會等到寫操作完成后才能繼續執行,提高文件讀寫的執行效率。

  • dispatch_queue_set_specific 、dispatch_get_specific

這兩個API類似于objc_setAssociatedObject跟objc_getAssociatedObject,FMDB里就用到這個來防止死鎖,來看看FMDB的部分源碼

static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
//創建一個串行隊列來執行數據庫的所有操作
 _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);

 //通過key標示隊列,設置context為self
 dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);

當要執行數據庫操作時,如果在queue里面的block執行過程中,又調用了 indatabase方法,需要檢查是不是同一個queue,因為同一個queue的話會產生死鎖情況

- (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");
}

  • dispatch_apply
    dispatch_apply類似一個for循環,會在指定的dispatch queue中運行block任務n次,如果隊列是并發隊列,則會并發執行block任務,dispatch_apply是一個同步調用,block任務執行n次后才返回。
    簡單的使用方法:
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
//并發的運行一個block任務5次
dispatch_apply(5, queue, ^(size_t i) {
    NSLog(@"do a job %zu times",i+1);
});
NSLog(@"go on");

輸出結果:

GCDTests[10029:760640] do a job 2 times
GCDTests[10029:760640] do a job 1 times
GCDTests[10029:760640] do a job 3 times
GCDTests[10029:760640] do a job 5 times
GCDTests[10029:760640] do a job 4 times
GCDTests[10029:760640] go on

在某些場景下使用dispatch_apply會對性能有很大的提升,比如你的代碼需要以每個像素為基準來處理計算image圖片。同時dispatch apply能夠避免一些線程爆炸的情況發生(創建很多線程)

//危險,可能導致線程爆炸以及死鎖
for (int i = 0; i < 999; i++){
   dispatch_async(q, ^{...});
}
dispatch_barrier_sync(q, ^{});

// 較優選擇, GCD 會管理并發
dispatch_apply(999, q, ^(size_t i){...});

Dispatch Block

添加到gcd隊列中執行的任務是以block的形式添加的,block封裝了需要執行功能,block帶來的開發效率提升就不說了,gcd跟block可以說是一對好基友,能夠很好的配合使用。

  • 創建block
    我們可以自己創建block并添加到queue中去執行
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
//創建block
dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"do something");
    });
dispatch_async(queue, block);

在創建block的時候我們也可以通過設置QoS,指定block對應的優先級,在dispatch_block_create_with_qos_class中指定QoS類別即可:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
        NSLog(@"do something with QoS");
    });
dispatch_async(queue, block);
  • dispatch_block_wait
    當需要等待前面的任務執行完畢時,我們可以使用dispatch_block_wait這個接口,設置等待時間DISPATCH_TIME_FOREVER會一直等待直到前面的任務完成:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create(0, ^{
    NSLog(@"before sleep");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"after sleep");
});
dispatch_async(queue, block);
//等待前面的任務執行完畢
dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
NSLog(@"coutinue");

程序運行結果:

GCDTests[16679:863641] before sleep
GCDTests[16679:863641] after sleep
GCDTests[16679:863529] coutinue
  • dispatch_block_notify
    dispatch_block_notify當觀察的某個block執行結束之后立刻通知提交另一特定的block到指定的queue中執行,該函數有三個參數,第一參數是需要觀察的block,第二個參數是被通知block提交執行的queue,第三參數是當需要被通知執行的block,函數的原型:
void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue,
        dispatch_block_t notification_block);

具體使用的方法:

    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t previousBlock = dispatch_block_create(0, ^{
        NSLog(@"previousBlock begin");
        [NSThread sleepForTimeInterval:1];
        NSLog(@"previousBlock done");
    });
    dispatch_async(queue, previousBlock);
    dispatch_block_t notifyBlock = dispatch_block_create(0, ^{
        NSLog(@"notifyBlock");
    });
    //當previousBlock執行完畢后,提交notifyBlock到global queue中執行
    dispatch_block_notify(previousBlock, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), notifyBlock);

運行結果:

GCDTests[17129:895673] previousBlock begin
GCDTests[17129:895673] previousBlock done
GCDTests[17129:895673] notifyBlock
  • dispatch_block_cancel
    之前在介紹nsopreration的時候提到它的一個優點是可以取消某個operation,現在在iOS8之后,提交到gcd隊列中的dispatch block也可取消了,只需要簡單的調用dispatch_block_cancel傳入想要取消的block即可:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
    NSLog(@"block1 begin");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"block1 done");
});
dispatch_block_t block2 = dispatch_block_create(0, ^{
    NSLog(@"block2 ");
});
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_block_cancel(block2);

可以看到如下的執行結果,block2不再執行了。

GCDTests[17271:902981] block1 begin
GCDTests[17271:902981] block1 done

Dispatch Group

當我們想在gcd queue中所有的任務執行完畢之后做些特定事情的時候,也就是隊列的同步問題,如果隊列是串行的話,那將該操作最后添加到隊列中即可,但如果隊列是并行隊列的話,這時候就可以利用dispatch_group來實現了,dispatch_group能很方便的解決同步的問題。dispatch_group_create可以創建一個group對象,然后可以添加block到該組里面,下面看下它的一些用法:

  • dispatch_group_wait
    dispatch_group_wait會同步地等待group中所有的block執行完畢后才繼續執行,類似于dispatch barrier
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
//將任務異步地添加到group中去執行
dispatch_group_async(group,queue,^{ NSLog(@"block1"); });
dispatch_group_async(group,queue,^{ NSLog(@"block2"); });
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
NSLog(@"go on");

執行結果如下,只有block1跟block2執行完畢后才會執行dispatch_group_wait后面的內容。

GCDTests[954:41031] block2
GCDTests[954:41032] block1
GCDTests[954:40847] go on
  • dispatch_group_notify
    功能與dispatch_group_wait類似,不過該過程是異步的,不會阻塞該線程,dispatch_group_notify有三個參數
void dispatch_group_notify(dispatch_group_t group, //要觀察的group
                           dispatch_queue_t queue,   //block執行的隊列
                           dispatch_block_t block);   //當group中所有任務執行完畢之后要執行的block

簡單的示意用法:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue,^{ NSLog(@"block1"); });
dispatch_group_async(group,queue,^{ NSLog(@"block2"); });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"done");
});
NSLog(@"go on");

可以看到如下的執行結果

GCDTests[1046:45104] go on
GCDTests[1046:45153] block1
GCDTests[1046:45152] block2
GCDTests[1046:45104] done
  • dispatch_group_enter dispatch_group_leave
    假如我們不想使用dispatch_group_async異步的將任務丟到group中去執行,這時候就需要用到dispatch_group_enter跟dispatch_group_leave方法,這兩個方法要配對出現,以下這兩種方法是等價的:
dispatch_group_async(group, queue, ^{ 
}); 

等價于

dispatch_group_enter(group);
dispatch_async(queue, ^{
  dispatch_group_leave(group);
});

簡單的使用方法,可以自己試試沒有寫dispatch_group_leave會發生什么。

dispatch_group_t group = dispatch_group_create();
for (int i =0 ; i<3; i++) {
    dispatch_group_enter(group);
    NSLog(@"do block:%d",i);
    dispatch_group_leave(group);
}
//等待上面的任務完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"go on");

Dispatch Semaphore

dispatch semaphore也是用來做解決一些同步的問題,dispatch_semaphore_create會創建一個信號量,該函數需要傳遞一個信號值,dispatch_semaphore_signal會使信號值加1,如果信號值的大小等于1,dispatch_semaphore_wait會使信號值減1,并繼續往下走,如果信號值為0,則等待。

//創建一個信號量,初始值為0
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"do some job");
    sleep(1);
    NSLog(@"increase the semaphore");
    dispatch_semaphore_signal(sema); //信號值加1
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);//等待直到信號值大于等1
NSLog(@"go on");

執行結果如下:

GCDTests[1394:92383] do some job
GCDTests[1394:92383] increase the semaphore
GCDTests[1394:92326] go on

Dispatch Timer

dispatch timer通常配合dispatch_after使用,完成一些延時的任務:

//延遲5秒后執行任務
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"do job afer 5 seconds");
});

Dispatch IO

當我們要讀取一份較大文件的時候,多個線程同時去讀肯定比一個線程去讀的速度要快,要實現這樣的功能可以通過dispatch io跟dispatch data來實現,通過dispatch io去讀文件時,會使用global dispatch queue將一個文件按照一個指定的分塊大小同時去讀取數據,類似于:

dispatch_async(queue, ^{/* 讀取0-99字節 */});
dispatch_async(queue, ^{/* 讀取100-199字節 */});
dispatch_async(queue, ^{/* 讀取200-299字節 */});
...

將文件分成一塊一塊并行的去讀取,讀取的數據通過Dispatch Data可以更為簡單地進行結合和分割 。

  • dispatch_io_create
    生成Dispatch IO,指定發生錯誤時用來執行處理的Block,以及執行該Block的Dispatch Queue
  • dispatch_io_set_low_water
    設定一次讀取的大小(分割的大小)
  • dispatch_io_read
    使用Global Dispatch Queue開始并列讀取,當每個分割的文件塊讀取完畢時,會將含有文件數據的dispatch data返回到dispatch_io_read設定的block,在block中需要分析傳遞過來的dispatch data進行合并處理

可以看下蘋果的系統日志API(Libc-763.11 gen/asl.c)的源代碼使用到了dispatch IO:源碼地址


//dispatch_io_create出錯時handler執行的隊列
pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
    //出錯時執行的handler
    close(fd);
});
*out_fd = fdpair[1];

//設定一次讀取的大小(分割大小)
dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
    if (error)
        return;
    if (err == 0)
    {
        //每次讀取到數據進行數據的處理
        size_t len = dispatch_data_get_size(pipedata);
        if (len > 0)
        {
            const char *bytes = NULL;
            char *encoded;
            uint32_t eval;
            dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
            encoded = asl_core_encode_buffer(bytes, len);
            asl_msg_set_key_val(aux, ASL_KEY_AUX_DATA, encoded);
            free(encoded);
            eval = _asl_evaluate_send(NULL, (aslmsg)aux, -1);
            _asl_send_message(NULL, eval, aux, NULL);
            asl_msg_release(aux);
            dispatch_release(md);
        }
    }
    if (done)
    {
        //并發讀取完畢
        dispatch_semaphore_signal(sem);
        dispatch_release(pipe_channel);
        dispatch_release(pipe_q);
    }
});

假如你的數據文件比較大,可以考慮采用dispatch IO的方式來提高讀取的速率。

Dispatch Source

dispatch框架提供一套接口用于監聽系統底層對象(如文件描述符、Mach端口、信號量等),當這些對象有事件產生時會自動把事件的處理block函數提交到dispatch隊列中執行,這套接口就是Dispatch Source API,Dispatch Source其實就是對kqueue功能的封裝,可以去查看dispatch_source的c源碼實現(什么是kqueue?Google,什么是Mach端口? Google Again),Dispatch Source主要處理以下幾種事件:

DISPATCH_SOURCE_TYPE_DATA_ADD   變量增加
DISPATCH_SOURCE_TYPE_DATA_OR    變量OR
DISPATCH_SOURCE_TYPE_MACH_SEND  Mach端口發送
DISPATCH_SOURCE_TYPE_MACH_RECV  Mach端口接收
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 內存壓力情況變化
DISPATCH_SOURCE_TYPE_PROC       與進程相關的事件
DISPATCH_SOURCE_TYPE_READ       可讀取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL     接收信號
DISPATCH_SOURCE_TYPE_TIMER      定時器事件
DISPATCH_SOURCE_TYPE_VNODE      文件系統變更
DISPATCH_SOURCE_TYPE_WRITE      可寫入文件映像

當有事件發生時,dispatch source自動將一個block放入一個dispatch queue執行。

  • dispatch_source_create
    創建一個dispatch source,需要指定事件源的類型,handler的執行隊列,dispatch source創建完之后將處于掛起狀態。此時dispatch source會接收事件,但是不會進行處理,你需要設置事件處理的handler,并執行額外的配置;同時為了防止事件堆積到dispatch queue中,dispatch source還會對事件進行合并,如果新事件在上一個事件處理handler執行之前到達,dispatch source會根據事件的類型替換或者合并新舊事件。

  • dispatch_source_set_event_handler
    給指定的dispatch source設置事件發生的處理handler

  • dispatch_source_set_cancel_handler
    給指定的dispatch source設置一個取消處理handler,取消處理handler會在dispatch soruce釋放之前做些清理工作,比如關閉文件描述符:

dispatch_source_set_cancel_handler(mySource, ^{ 
   close(fd); //關閉文件秒速符 
}); 
  • dispatch_source_cancel
    異步地關閉dispatch source,這樣后續的事件發生時不去調用對應的事件處理handler,但已經在執行的handler不會被取消。

很多第三方庫會用到dispatch source的功能,比如著名的IM框架XMPPFramework在涉及到定時器的時候都采用這種方法,比如發送心跳包的時候(setupKeepAliveTimer)。
一個簡單的例子:

//如果dispatch source是本地變量,會被釋放掉,需要這么聲明
@property (nonatomic)dispatch_source_t timerSource;

//事件handler的處理隊列
dispatch_queue_t queue = dispatch_queue_create("myqueue", NULL);

//
_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

//定時器間隔時間
uint64_t interval = 2 * NSEC_PER_SEC;
//設置定時器信息
dispatch_source_set_timer(_timerSource,DISPATCH_TIME_NOW, interval , 0);

//設置事件的處理handler
dispatch_source_set_event_handler(_timerSource, ^{
    NSLog(@"receive time event");
    //if (done) 
    //   dispatch_source_cancel(_timerSource); 
});
//開始處理定時器事件,dispatch_suspend暫停處理事件
dispatch_resume(_timerSource);

定時器還可以通過NSTimer實現,不過NSTimer會跟runloop關聯在一起,主線層默認有一個runloop,假如你nstimer是運行在子線程,就需要自己手動開啟一個runloop,而且nstimer默認是在NSDefaultRunLoopMode模式下的,所以當runloop切換到其它模式nstimer就不會運行,需要手動將nstimer添加到NSRunLoopCommonModes模式下;而dispatch source timer不跟runloop關聯,所以有些場景可以使用這種方法。

本文總結了GCD的一些用法,不過有些API可能iOS8之后才可以用,如有還有什么可以補充的,歡迎提出~

部分參考

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

推薦閱讀更多精彩內容

  • 目錄(GCD): 關鍵詞 混淆點 場景應用 總結 1. 關鍵詞 線程概念: 獨立執行的代碼段,一個線程同時間只能執...
    Ryan___閱讀 1,282評論 0 3
  • 程序中同步和異步是什么意思?有什么區別? 解釋一:異步調用是通過使用單獨的線程執行的。原始線程啟動異步調用,異步調...
    風繼續吹0閱讀 1,039評論 1 2
  • 在這兩部分的系列中,第一個部分的將解釋 GCD 是做什么的,并從許多基本的 GCD 函數中找出幾個來展示。在第二部...
    透支未來閱讀 358評論 0 1
  • GCD筆記 總結一下多線程部分,最強大的無疑是GCD,那么先從這一塊部分講起. Dispatch Queue的種類...
    jins_1990閱讀 774評論 0 1
  • 背景 擔心了兩周的我終于輪到去醫院做胃鏡檢查了!去的時候我都想好了最壞的可能(胃癌),之前在網上查的癥狀都很相似。...
    Dely閱讀 9,257評論 21 42