iOS超級超級詳細介紹GCD

FlyOceanFish發表於2018-01-17

什麼是GCD

Grand Central Dispatch(GCD)是非同步執行任務的技術之一。一般將應用程式中記述的執行緒管理用的程式碼在系統級中實現。開發者只需要定義想執行的任務並追加到適當的Dispatch Queue中,GCD就能生成必要的執行緒並計劃執行任務。由於執行緒管理是作為系統的一部分來實現的,因此可統一管理,也可執行任務,這樣就比以前的執行緒更有效率。

Dispatch Queue

將想執行的任務追加到適當的Dispatch Queue

Dispatch Queue種類 說明
Serial Dispatch Queue 序列佇列,任務按照追加順序處理(FIFO)
Concurrent Dispatch Queue 並行佇列

序列佇列

並行佇列.jpg

並行佇列

1516067701212.jpg

重點

序列佇列只有一個執行緒,並行佇列有多個執行緒。

自建Queue

dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);//建立了一個並行佇列

dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_SERIAL);//建立了一個序列佇列

複製程式碼

系統Queue

  • Main DispatchQueue 在主執行緒中執行的Dispatch Queue。因為主執行緒只有1個,所以Main Dispatch Queue是序列佇列。加入到主佇列中的任務一定不會生成新的執行緒,因為主佇列必須有且只有一條主執行緒。
  • Global Dispatch Queue 一個所有應用程式都能夠使用的併發佇列。加入到該佇列中的任務不一定會生成執行緒。因為有執行緒重用的現象

iOS8.0之後的許可權

名稱 描述
QOS_CLASS_USER_INTERACTIVE 與使用者互動的任務,這些任務通常跟UI級別的重新整理相關,比如動畫,這些任務需要在一瞬間完成
QOS_CLASS_USER_INITIATED 由使用者發起的並且需要立即得到結果的任務,比如滑動scroll view時去載入資料用於後續cell的顯示,這些任務通常跟後續的使用者互動相關,在幾秒或者更短的時間內完成
QOS_CLASS_DEFAULT 優先順序介於user-initiated 和 utility,當沒有 QoS資訊時預設使用,開發者不應該使用這個值來設定自己的任務
QOS_CLASS_UTILITY 一些可能需要花點時間的任務,這些任務不需要馬上返回結果,比如下載的任務,這些任務可能花費幾秒或者幾分鐘的時間
QOS_CLASS_BACKGROUND 這些任務對使用者不可見,比如後臺進行備份的操作,這些任務可能需要較長的時間,幾分鐘甚至幾個小時
QOS_CLASS_UNSPECIFIED 未指定

Dispatch Group

dispatch_group是GCD的一項特性,能夠把任務分組。呼叫者可以等待這組任務執行完畢,也可以提供回撥函式之後繼續往下執行,這組任務完成時,呼叫者會得到通知。常用場景比如說,下載一個大的檔案,分塊下載,全部下載完成後再合成一個檔案。再比如同時下載多個圖片,監聽全部下載完後的動作

  • 建立group

dispatch_group_t group = dispatch_group_create();

  • 新增任務

    • dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);

      將一個任務新增到指定group中

    • dispatch_group_enter(dispatch_group_t group); dispatch_group_leave(dispatch_group_t group);

      這兩個函式同上邊一樣的效果,不過一定要注意這兩個函式必須成對出現!否則這一組任務就永遠執行不完。

  • 監聽任務完成

    • dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block); 開發者可以傳入block,等dispatch group 執行完畢之後,塊會在特定的執行緒上執行,而不阻塞執行緒。
    • long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

    timeout參數列示函式在等待dispatch group執行完畢後,應該阻塞多久。如果執行dispatch group所需的時間小於timeout,則返回0,否則返回非0值.此引數可以取常量DISPATCH_TIME_FOREVER,這表示函式會一直等著dispatch group 執行完,而不會超時。此方法會阻塞執行緒。

*** 使用dispatch_group_notify***

//這個例子演示將將一個很大的字串刪除指定幾個字串。比如“abcdefghicladedgdsfs”刪除"ace"三個字串

  int const COUNT = 6;
@interface ViewController ()
{
    NSMutableArray *values;
    dispatch_group_t group;
    dispatch_queue_t queue;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    values = [NSMutableArray array];
    group = dispatch_group_create();
    queue = dispatch_queue_create("com.aa.test", DISPATCH_QUEUE_CONCURRENT);
    [self removeChars:@"abcdefghicladedgdsfs" target:@"ace"];
    dispatch_group_notify(group, queue, ^{
        NSString *str = [values componentsJoinedByString:@""];
        NSLog(@"%@",str);
    });
}
- (void) removeChars:(NSString *)pBuffer target:(NSString *)target {
    for (int i = 0; i<COUNT; i++) {
        if (i==COUNT-1) {
            NSString *str = [pBuffer substringWithRange:NSMakeRange(pBuffer.length/(COUNT-1)*i,pBuffer.length-pBuffer.length/(COUNT-1)*i)];
            [values addObject:str];
        }else{
            NSString *str = [pBuffer substringWithRange:NSMakeRange(pBuffer.length/(COUNT-1)*i, pBuffer.length/(COUNT-1))];
            [values addObject:str];
        }
    }
    for (int i = 0; i<values.count; i++) {
       [self handlerString:values[i] target:target index:i];
    }
}
- (void)handlerString:(NSString *)string target:(NSString *)target index:(int)index{
    dispatch_group_async(group, queue, ^{
        values[index] = [self replace:string target:target];
    });
}
- (NSString *)replace:(NSString *)string target:(NSString *)target{
    for (int i = 0; i<target.length; i++) {
        char temp = [target characterAtIndex:i];
        string = [string stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"%c", temp] withString:@""];
    }
    return string;
}

@end
複製程式碼

使用dispatch_group_enter和dispatch_group_leave

dispatch_group_t group =dispatch_group_create();
dispatch_queue_t globalQueue=dispatch_get_global_queue(0, 0);


dispatch_group_enter(group);

//模擬多執行緒耗時操作
dispatch_group_async(group, globalQueue, ^{
    sleep(3);
    NSLog(@"%@---block1結束。。。",[NSThread currentThread]);
    dispatch_group_leave(group);
});
NSLog(@"%@---1結束。。。",[NSThread currentThread]);

dispatch_group_enter(group);
//模擬多執行緒耗時操作
dispatch_group_async(group, globalQueue, ^{
    sleep(3);
    NSLog(@"%@---block2結束。。。",[NSThread currentThread]);
    dispatch_group_leave(group);
});
NSLog(@"%@---2結束。。。",[NSThread currentThread]);

dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
    NSLog(@"%@---全部結束。。。",[NSThread currentThread]);
});
複製程式碼

dispatch_asyncdispatch_sync

  • dispatch_async

dispatch_async將指定的Block非同步的追加到指定的Dispatch Queue中。dispatch_async函式不會做任何等待

  • dispatch_sync

dispatch_sync將指定的Block同步的追加到指定的Dispatch Queue。此時dispatch_sync會一直等待Block執行結束之後,才會返回。執行緒才能接著繼續執行其他程式碼。

如dispatch_group_wait函式一樣,"等待"意味著當前執行緒停止。

例:

dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"測試2");
    });
複製程式碼

執行以上程式碼則會直接崩潰。主要是因為dispatch_sync在追加Block的過程中同時在等待,等待意味著當前主執行緒已經停止,所以主執行緒無法執行追到到Main Dispatch Queue的Block。

Serial Dispatch Queue也會引起同樣的問題

dispatch_queue_t queue = dispatch_queue_create("com.test.queue", NULL);
dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"測試");
        });
    });
複製程式碼

一樣的道理,序列佇列中只會生成一條執行緒,而dispatch_sync函式使該執行緒處於等待狀態即停止狀態,所以無法將Block追加到com.test.queue這個佇列中。

總結:

  • dispatch_sync函式

    • 當前queue是序列佇列。 當前queue上呼叫sync函式,並且sync函式中指定的queue也是當前queue。需要執行的block被放到當前queue的隊尾等待執行,因為這是一個序列的queue,呼叫sync函式會阻塞當前佇列,等待block執行 這個block永遠沒有機會執行sync函式不返回,所以當前佇列就永遠被阻塞了,這就造成了死鎖。(這就是問題中在主執行緒呼叫sync函式,並且在sync函式中傳入main_queue作為queue造成死鎖的情況)
    • 當前queue是並行佇列。 在並行的queue上面呼叫sync函式,同時傳入當前queue作為引數,並不會造成死鎖,因為block會馬上被執行,所以sync函式也不會一直等待不返回造成死鎖。(並且Block是在當前執行緒上執行。例如如果是在主執行緒上呼叫了dispatch_sync,則Block是在主執行緒上執行的)
  • dispatch_async代表非同步任務,意思不是一定會生成一條執行緒。如果在MainQueue中執行,則不會生成執行緒;如果在Global Queue中有可能會生成。因為執行緒有一個執行緒池,會重用已經完成任務了的執行緒。

柵欄(dispatch_barrier_sync和dispatch_barrier_async)

作用:與併發佇列結合,可以高效率的避免資料競爭的問題

相同點:dispatch_barrier_syncdispatch_barrier_async函式功能一樣就是在併發佇列中將此程式碼插入的地方上下隔開,如果柵欄一樣,兩部分不影響。只有上邊的併發佇列都執行結束之後,下邊的併發佇列才能夠執行。

1516067701212.jpg

不同點:dispatch_barrier_sync程式碼後邊的任務直到dispatch_barrier_sync執行完才能被追加到佇列中;dispatch_barrier_async不用程式碼執行完,後邊的任務也會被追加到佇列中。程式碼上的體現就是dispatch_barrier_sync後邊的程式碼不會執行,dispatch_barrier_async後邊的程式碼會執行,但是Block不會被執行。

dispatch_apply

This function submits a block to a dispatch queue for multiple invocations and waits for all iterations of the task block to complete before returning. If the target queue is a concurrent queue returned by dispatch_get_global_queue, the block can be invoked concurrently, and it must therefore be reentrant-safe. Using this function with a concurrent queue can be useful as an efficient parallel for loop.

該函式指定次數將指定的Block追加到指定的Queue中,並等待全部處理執行結束。

dispatch_apply(10, dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^(size_t index) {
        
 });
複製程式碼

作用:與併發佇列結合,可以更有效執行併發任務。

dispatch_suspend/dispatch_resume

掛起指定的Dispatch Queue

dispatch_suspend(queue);
複製程式碼

恢復指定的Dispatch Queue

dispatch_resume(queue);
複製程式碼

Dispatch Semaphore

當計數為0時等待,計數為1或大於1時不等待

dispatch_semaphore_create(1);//建立1個訊號
複製程式碼
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//當計數值大於1時,或者在待機中計數值大於1時,對該計數減1並且返回。
複製程式碼
dispatch_semaphore_signal(semaphore);//對計數值加1
複製程式碼

dispatch_once

單例模式,保證在應用程式中只執行一次指定處理的api。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
        
 });
複製程式碼

Dispatch I/O

非同步序列讀取檔案

NSString *path = [[NSBundle mainBundle] pathForResource:@"iosintroduce" ofType:@"md"];
dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
dispatch_io_t pipe_chanel = dispatch_io_create_with_path(DISPATCH_IO_STREAM,[path UTF8String], 0, 0,queue , ^(int error) {
        
    });
size_t water = 1024;
dispatch_io_set_low_water(pipe_chanel, water);
dispatch_io_set_high_water(pipe_chanel, water);
NSMutableData *totalData = [[NSMutableData alloc] init];
    
dispatch_io_read(pipe_chanel, 0, SIZE_MAX, queue, ^(bool done, dispatch_data_t  _Nullable data, int error) {
    if (error == 0) {
        size_t len = dispatch_data_get_size(data);
            if (len > 0) {
                [totalData appendData:(NSData *)data];
        }
    }
    if (done) {
        NSString *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];
        NSLog(@"%@", str);
    }

  });
複製程式碼

非同步並行讀取檔案

NSString *path = [[NSBundle mainBundle] pathForResource:@"iosintroduce" ofType:@"md"];
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_fd_t fd = open(path.UTF8String, O_RDONLY);
dispatch_io_t io = dispatch_io_create(DISPATCH_IO_RANDOM, fd, queue, ^(int error) {

    close(fd);

});

off_t currentSize = 0;

long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;

size_t offset = 1024*1024;

dispatch_group_t group = dispatch_group_create();

NSMutableData *totalData = [[NSMutableData alloc] initWithLength:fileSize];

for (; currentSize <= fileSize; currentSize += offset) {

    dispatch_group_enter(group);

    dispatch_io_read(io, currentSize, offset, queue, ^(bool done, dispatch_data_t  _Nullable data, int error) {

        if (error == 0) {

            size_t len = dispatch_data_get_size(data);

            if (len > 0) {

                const void *bytes = NULL;

                (void)dispatch_data_create_map(data, (const void **)&bytes, &len);

                [totalData replaceBytesInRange:NSMakeRange(currentSize, len) withBytes:bytes length:len];

            }

        }
        if (done) {

            dispatch_group_leave(group);

        }

    });

}
dispatch_group_notify(group, queue, ^{

    NSString *str = [[NSString alloc] initWithData:totalData encoding:NSUTF8StringEncoding];

    NSLog(@"%@", str);

});
複製程式碼
  • dispatch_io_create_with_path通過路徑建立一個dispatch_io_t。與之相似的也好幾個方法,比如dispatch_io_create_with_io
  • dispatch_io_set_high_waterdispatch_io_set_low_water 分割檔案大小,分別可以設定一次最少讀取和一次最多讀取多大。
  • dispatch_io_read讀取檔案。與之對應的是dispatch_io_write將檔案儲存到指定路徑

如果想提高檔案讀取速度,可以嘗試使用Dispatch I/O。

Dispatch Source

倒數計時功能

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 15ULL*NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 1ull*NSEC_PER_SEC);
    
dispatch_source_set_event_handler(timer, ^{
        NSLog(@"wake up");
        dispatch_source_cancel(timer);
  });
    
dispatch_source_set_cancel_handler(timer, ^{
        NSLog(@"canceled");
 });
    
dispatch_resume(timer);
複製程式碼

dispatch_after

延遲執行任務

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
    });
複製程式碼

最快3秒後執行,最慢3秒+一個runloop迴圈時間

dispatch_set_target_queue

改變生成的Dispatch Queue的執行優先順序

//將myQueue優先順序設定為與globalQueue相同

dispatch_queue_t myQueue = dispatch_queue_create("com.test.queue", NULL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
dispatch_set_target_queue(myQueue, globalQueue)
複製程式碼

第一個引數需要變更優先順序的Dispatch Queue,第二個引數指定要使用的執行優先順序相同優先順序的Queue。

個人部落格

已經同步釋出到FlyOceanFish的部落格

相關文章