iOS超級超級詳細介紹GCD

什麼是GCD

Grand Central Dispatch(GCD)是異步執行任務的技術之一。通常將應用程序中記述的線程管理用的代碼在系統級中實現。開發者只須要定義想執行的任務並追加到適當的Dispatch Queue中,GCD就能生成必要的線程並計劃執行任務。因爲線程管理是做爲系統的一部分來實現的,所以可統一管理,也可執行任務,這樣就比之前的線程更有效率。ios

Dispatch Queue

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

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

串行隊列

並行隊列.jpg

並行隊列

1516067701212.jpg

重點

串行隊列只有一個線程,並行隊列有多個線程。bash

自建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以後的權限markdown

名稱 描述
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();併發

  • 添加任務app

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

      將一個任務添加到指定group中async

    • 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的博客

相關文章
相關標籤/搜索