OC底層原理二十六:GCD詳解(上)

OC底層原理 學(xué)習大綱

上一節(jié)我們分析了多線程的知識捞附,本節(jié)碳蛋,我們著重分析多線程中使用最頻繁GCD

  1. GCD簡介
  2. 函數(shù)與隊列四大組合(同步、異步蚁袭、串行腮敌、并行)
  3. 性能調(diào)度耗能
  4. 面試題
  5. 線程資源共享
  6. 柵欄函數(shù)barrier
  7. 調(diào)度組 Group
  8. GCD單例
  9. 信號量 semaphore

1. GCD簡介

GCD阱当,全稱Grand Central Dispatch(中央調(diào)度中心)俏扩,純C語言開發(fā),提供了很多強大的函數(shù)弊添。

  • GCD的優(yōu)勢
  1. GCD是蘋果公司為多核并行運算提出的解決方案录淡;
  2. GCD會自動利用更多的CPU內(nèi)核(比如雙核、四核等)油坝;
  3. GCD會自動管理線程的生命周期(創(chuàng)建線程嫉戚、調(diào)度任務(wù)、銷毀線程)澈圈。

程序員只需要告訴GCD想要執(zhí)行的任務(wù)彬檀,不需要編寫任何線程管理相關(guān)代碼(調(diào)度銷毀不用管)

  • GCD核心: 將任務(wù)添加到隊列瞬女,并指定執(zhí)行任務(wù)的函數(shù)

這里引申出任務(wù)窍帝、隊列執(zhí)行任務(wù)的函數(shù)三個內(nèi)容拆魏。我們一一進行分析

首先盯桦,我們展示一個簡單示例

- (void)syncTest {

    // 任務(wù)(block)
    dispatch_block_t block = ^{
        NSLog(@"hello GCD");
    };

    // 隊列(此處串行隊列)
    dispatch_queue_t queue = dispatch_queue_create("ht-syncTest", DISPATCH_QUEUE_SERIAL);

    // 執(zhí)行任務(wù)的函數(shù)(此處異步函數(shù))
    dispatch_async(queue, block);

}
  • 借助示例,我們可以很好的理解任務(wù)渤刃、隊列執(zhí)行任務(wù)的函數(shù)

1.1 任務(wù)

GCD的任務(wù)是使用block封裝的函數(shù)拥峦,沒有入?yún)?/code>和返參

  • 任務(wù)創(chuàng)建好后卖子,等待執(zhí)行任務(wù)的函數(shù)將其放入隊列中略号。

拓展:

  • 執(zhí)行block,需要調(diào)用block()洋闽,這步調(diào)用玄柠,是執(zhí)行任務(wù)的函數(shù)內(nèi)部自動管理
    后面解析dispatch源碼時诫舅,可以清楚知道調(diào)用時機羽利。

1.2 隊列

GCD的隊列包含串行隊列并行隊列兩種。

  • 串行隊列: 同一時刻只允許一個任務(wù)執(zhí)行刊懈。(類似單車道这弧,汽車只能一輛輛排隊通過
  • 并行隊列: 同一時刻允許多個任務(wù)執(zhí)行。(類似多車道虚汛,同時可以多輛汽車通過
image.png

1.3 執(zhí)行任務(wù)的函數(shù)

執(zhí)行任務(wù)的函數(shù)包括同步函數(shù)異步函數(shù)兩種:

1.3.1 dispatch_sync同步函數(shù):
  • 必須等待當前語句執(zhí)行完畢匾浪,執(zhí)行下一條語句
  • 開啟線程,就在當前線程執(zhí)行block任務(wù)
1.3.2 dispatch_async異步函數(shù):
  • 不用等待當前語句執(zhí)行完畢卷哩,就可以執(zhí)行下一條語句
  • 開啟線程執(zhí)行block任務(wù)
    (在新線程執(zhí)行還是空閑舊線程執(zhí)行蛋辈,取決cpu的調(diào)度)

異步多線程代名詞

  • 多線程意義,就是為了適當提高執(zhí)行效率将谊,開啟多個線程"同時"執(zhí)行多個任務(wù)冷溶。
  • 嚴格來說渐白,應(yīng)該是并行異步多線程的代名詞。因為只有并行挂洛,才支持多通道(車道),才能同時執(zhí)行多個任務(wù)礼预。

ps: (下文中提到的函數(shù),都指代執(zhí)行任務(wù)的函數(shù)

為了更好的理解這些概念虏劲,下面對函數(shù)與隊列四大組合一一進行案例分析

2. 函數(shù)與隊列四大組合(同步托酸、異步、串行柒巫、并行)

未命名.png
  • 主隊列dispatch_get_main_queue
  1. 專門用來在主線程調(diào)度任務(wù)串行隊列
  2. 開啟線程
  3. 如果當前主線程正在執(zhí)行任務(wù)励堡,需要當前任務(wù)執(zhí)行完,才會繼續(xù)調(diào)度其他任務(wù)堡掏。
  • 全局并發(fā)隊列dispatch_get_global_queue
  1. 為了方便程序員的使用应结,蘋果提供了全局隊列 (并發(fā)隊列,實現(xiàn)多線程需求的快捷方式)泉唁。
  2. 使用多線程開發(fā)時鹅龄,如果對隊列沒有特殊要求,可直接使用全局隊列來執(zhí)行異步任務(wù)亭畜。)

拓展:
Q:隊列有幾種扮休?

- (void)demo {
    // 串行隊列
   dispatch_queue_t serial = dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL);
   // 并行隊列
   dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
   // 主隊列(串行隊列)
   dispatch_queue_t mainQueue = dispatch_get_main_queue();
   // 全局隊列 (并行隊列)
   dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

   NSLog(@"\n%@ \n%@ \n%@ \n%@", serial, concurrent, mainQueue, globalQueue);
}
image.png
  • A:只有串行隊列并行隊列兩種。
    (底層: DQF_WIDTH為1:表示串行隊列拴鸵,DQF_WIDTH大于1: 表示并行隊列 玷坠。詳細底層分析,下一節(jié)會講)

2.1 同步 + 串行 死鎖

- (void)mainSyncTest{
    
    NSLog(@"0 %@", [NSThread currentThread]);
    // 等
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"1 %@", [NSThread currentThread]);
    });
    NSLog(@"2 %@", [NSThread currentThread]);
}
  • 打印結(jié)果(打印0之后劲藐,崩潰):
image.png
  • 分析:
  1. 主隊列(main)是串行隊列八堡,函數(shù)是sync同步函數(shù)。屬于同步函數(shù)&串行隊列的情況
  2. 在打印0之后聘芜,dispatch_sync同步函數(shù)將block排隊插入mainSyncTest函數(shù)最后兄渺,等待mainSyncTest函數(shù)執(zhí)行完后再執(zhí)行。
  3. 但是block沒有執(zhí)行汰现,dispatch_sync函數(shù)就等于沒有完成挂谍。程序無法往下執(zhí)行。
  4. 所以造成了dispatch_syncmainSyncTest執(zhí)行完后執(zhí)行block服鹅,而mainSyncTest卻說dispatch_sync沒有執(zhí)行完凳兵,我無法結(jié)束百新。 ??
image.png

這里有一個誤區(qū)企软,堵塞打印2無關(guān)。

image.png

真正的堵塞饭望,是由于dispatch_sync內(nèi)部的block需要等mainSyncTest全部執(zhí)行完再執(zhí)行仗哨,而mainSyncTest函數(shù)需要等dispatch_sync執(zhí)行完形庭。

  • 借用一個笑話描述:
    面試官:你講清楚了GCD的底層原理,我就錄用你厌漂。
    大牛:你錄用我萨醒,我就給你講GCD的底層原理

加深理解,分享今日騰訊前輩指點

Q: 如果上述代碼苇倡,將同步 + 主隊列執(zhí)行改為同步 + 自定義串行隊列富纸,是否會堵塞?

image.png

  • 【疑問點】: 雖然將主隊列執(zhí)行改為自定義串行隊列旨椒,解決了堵塞問題晓褪,但是否和syncTest代碼本身運行的隊列線程有關(guān)呢?

[之前堵塞代碼] 函數(shù)代碼block代碼都在主隊列+主線程
[新代碼]函數(shù)代碼自定義隊列+主線程

  • 本著求真的原則综慎,我將syncTest代碼也放在這個自定義隊列中執(zhí)行涣仿,此時堵塞又出現(xiàn)了:

    image.png

  • 我們注意到線程還是在main主線程。所以:
    同一隊列同一線程進行sync同步操作示惊,會阻塞,Crash

  • 繼續(xù)探索,按照上面說的纵装,自定義隊列主隊列相满,是否也可以阻止阻塞呢?
    答案是錯誤

    image.png

主隊列是個特殊隊列阔拳,APP啟動時就與主線程完成了線程綁定崭孤。不會切換線程

  • 繼續(xù)探索糊肠,是不是由于主隊列特殊
  1. 自定義隊列其他自定義隊列辨宠,是否也可以阻止阻塞呢?
    答案是正確
    image.png
    1. 是否和主線程有關(guān)货裹?如果當前函數(shù)子線程執(zhí)行嗤形,任務(wù)回歸主線程操作,是否也可以阻止阻塞呢弧圆?
      答案是正確
      image.png

總結(jié):

主隊列是個特殊隊列赋兵,APP啟動時就與主線程完成了線程綁定,不會切換線程

  1. 當前環(huán)境:主隊列+主線程搔预,執(zhí)行sync同步 + main_queue主隊列任務(wù)霹期,會阻塞
  2. 當前環(huán)境:主隊列+主線程,切換到自定義串行隊列拯田,不會開辟線程(blockmain線程執(zhí)行),不會阻塞
  3. 當前環(huán)境:自定義串行隊列+主線程历造,切換到主隊列會阻塞
  4. 當前環(huán)境:自定義串行隊列+主線程,切換到新自定義串行隊列吭产,不會開辟線程(blockmain線程執(zhí)行),不會阻塞
  5. 當前環(huán)境:自定義串行隊列+子線程侣监,切換到主隊列,主隊列是綁定main線程的臣淤,所以會切換回main線程執(zhí)行block任務(wù)橄霉,執(zhí)行完后回到子線程,執(zhí)行后續(xù)任務(wù)邑蒋。不會阻塞

2.2 同步 + 并行

- (void)globalSyncTest{
    
    for (int i = 0; i<20; i++) {
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}
image.png
  • 阻塞線程姓蜂,但是一次只通過一個。是耗時操作医吊。

2.3 異步 + 串行

- (void)mainAsyncTest{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"1 %@", [NSThread currentThread]);
    });
    NSLog(@"2 %@", [NSThread currentThread]);
}
image.png
  • 可以發(fā)現(xiàn)覆糟,異步+串行時,異步函數(shù)內(nèi)的Block(打印1)是在mainAsyncTest函數(shù)全部執(zhí)行完后(打印了2)遮咖,再在新線程中執(zhí)行block滩字,打印了1。

可以對比上面2.1 同步 + 串行 阻塞死鎖的現(xiàn)象御吞,兩者的區(qū)別是:

同步 + 串行:

  1. dispatch_sync必須 mainSyncTest執(zhí)行完麦箍,才將block任務(wù)插入尾部。

  2. dispatch_sync必須 block執(zhí)行完陶珠,才算完成挟裂。

異步 + 串行:

1.dispatch_async 不用等 mainAsyncTest執(zhí)行完,直接將block任務(wù)插入尾部揍诽。

  1. dispatch_async 不用等 block執(zhí)行完诀蓉,只要將block插入尾部,就算完成了暑脆。

2.3 異步 + 并行

- (void)globalSyncTest{

    for (int i = 0; i<20; i++) {
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"hello queue");
}
image.png
  • 開啟多個線程渠啤,執(zhí)行順序不確定

3. 性能調(diào)度耗能

測試代碼:

- (void)dissipation {
    
    CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    
    dispatch_queue_t queue = dispatch_queue_create("ht-thread", DISPATCH_QUEUE_SERIAL);
    
//    dispatch_async(queue, ^{
//        NSLog(@"異步執(zhí)行");
//    });
    
    dispatch_sync(queue, ^{
        NSLog(@"同步執(zhí)行");
    });
    
    NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
    
}
  • 對比無任何操作添吗、創(chuàng)建線程沥曹、創(chuàng)建線程調(diào)用同步函數(shù)創(chuàng)建線程調(diào)用異步函數(shù)四種情況的耗時:
  1. 無任何操作時碟联,基本無耗時

    image.png

  2. 創(chuàng)建線程: 耗時0.00009秒

    image.png

  3. 創(chuàng)建線程且調(diào)用異步函數(shù): 耗時0.00040秒

    image.png

  4. 創(chuàng)建線程且調(diào)用同步函數(shù): 耗時0.000232秒

    image.png

結(jié)論

  1. 每次創(chuàng)建線程妓美,都會有時間上的損耗
  2. 線程創(chuàng)建后,同步執(zhí)行異步執(zhí)行耗時

4. 面試題

4.1 面試題一

- (void)demo{
    // 串行隊列
    dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    // 異步函數(shù)
    dispatch_async(queue, ^{
        NSLog(@"2");
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"3");
        });
    });
    NSLog(@"5");
}
image.png
  • 打印1鲤孵、5壶栋、2后,崩潰普监。

如果你掌握上面內(nèi)容贵试,特別[圖片上傳中...(未命名.png-beed7-1604486756600-0)]
是我總結(jié)的同步 + 串行異步 + 串行的區(qū)別熟悉了丧没。這題就難不住你了。

分析:


image.png

4.2 面試題二

- (void)textDemo{
    // 并行隊列
    dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1 %@",[NSThread currentThread]);
    // 異步
    dispatch_async(queue, ^{
        NSLog(@"2 %@",[NSThread currentThread]);
        // 同步
        dispatch_sync(queue, ^{
            NSLog(@"3 %@",[NSThread currentThread]);
        });
        NSLog(@"4 %@",[NSThread currentThread]);
    });
    NSLog(@"5 %@",[NSThread currentThread]);
}
image.png
  • 打印結(jié)果: 1 -> 5 -> 2 -> 3 -> 4

  • 面試題一不同锡移,這里是DISPATCH_QUEUE_CONCURRENT并行隊列。

  • 參考2.2 同步+并行分析漆际,并發(fā)隊列中的dispatch_sync同步函數(shù)阻塞線程淆珊,但是一次只通過一個任務(wù)

4.3 面試題三

- (void)textDemo{
    // 并行隊列
    dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    // 耗時
    dispatch_async(queue, ^{
        NSLog(@"2");
        dispatch_async(queue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
image.png
  • 打印結(jié)果: 1 -> 5 -> 2 -> 4 -> 3

4.4 面試題四

  • 選出打印順序可能出現(xiàn)的選項:
    A: 1230789
    B: 1237890
    C: 3120798
    D: 2137890
- (void)demo{
    // 并行隊列
    dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"1");
    });
    dispatch_async(queue, ^{
        NSLog(@"2");
    });
    
    // 同步
    dispatch_sync(queue, ^{
        NSLog(@"3");
    });
    
    NSLog(@"0");

    dispatch_async(queue, ^{
        NSLog(@"7");
    });
    dispatch_async(queue, ^{
        NSLog(@"8");
    });
    dispatch_async(queue, ^{
        NSLog(@"9");
    });
}
image.png
  • 答案是 AC

分析:

  1. 并發(fā)隊列奸汇;
  2. 異步 & 并發(fā)無序的施符,所以12的打印是無序的, 7擂找、8戳吝、9的打印是無序的;
  3. 同步 & 并發(fā)排隊一個個任務(wù)執(zhí)行贯涎,所以0一定在3后面打印听哭,7、8塘雳、9一定在0后面打印陆盘。

滿足03后打印,7败明、8隘马、90后打印。只有選項 AC妻顶。


5. 線程資源共享

  • 多讀單寫:
    利用串行隊列酸员,異步函數(shù)支持多人買票同步函數(shù)限制同一時刻僅出一張票讳嘱。
@interface ViewController ()
@property (nonatomic, assign) NSInteger tickets;      // 票數(shù)
@property (nonatomic, strong) dispatch_queue_t queue; // 隊列
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 準備票數(shù)
    _tickets = 20;
    // 創(chuàng)建串行隊列
    _queue = dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL);
    
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    // 第一個線程賣票
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self saleTickes];
    });

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 第二個線程賣票
        [self saleTickes];
    });

}

- (void)saleTickes {
    
    while (self.tickets > 0) {
        // 模擬延時
        [NSThread sleepForTimeInterval:1.0];
        // 蘋果不推薦程序員使用互斥鎖幔嗦,串行隊列同步任務(wù)可以達到同樣的效果!
        // @synchronized
        // 使用串行隊列沥潭,同步任務(wù)賣票
        dispatch_sync(_queue, ^{
            // 檢查票數(shù)
            if (self.tickets > 0) {
                self.tickets--;
                NSLog(@"還剩 %zd %@", self.tickets, [NSThread currentThread]);
            } else {
                NSLog(@"沒有票了");
            }
        });
    }
}
@end

6. 柵欄函數(shù)barrier

控制任務(wù)執(zhí)行順序崭添,同步

  • dispatch_barrier_async: 前面任務(wù)都執(zhí)行完畢叛氨,才會到這里(不會堵塞線程)

  • dispatch_barrier_sync: 堵塞線程呼渣,等待前面任務(wù)都執(zhí)行完畢,才放開堵塞寞埠。堵塞期間屁置,后面的任務(wù)都被掛起等待。

重點:柵欄函數(shù)只能控制同一并發(fā)隊列

  • 柵欄函數(shù)只應(yīng)用在并行隊列&異步函數(shù)中仁连,它的作用就是在監(jiān)聽多個信號(任務(wù))是否都完成蓝角。
    ( 串行同步內(nèi)的信號(任務(wù))本身就是按順序執(zhí)行阱穗,不需要使用到柵欄函數(shù)。)

坑點:柵欄函數(shù)為何不能使用dispatch_get_global_queue隊列使鹅?

因為global隊列中有很多系統(tǒng)任務(wù)也在執(zhí)行揪阶。 我們需要dispatch_queue_create手動創(chuàng)建一個純凈隊列,放置自己需要執(zhí)行的任務(wù)患朱,再使用柵欄函數(shù)監(jiān)聽任務(wù)的執(zhí)行結(jié)果鲁僚。

//MARK: -ViewController
@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    __block CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    
    // 請求token
    [self requestToken:^(id value) {
        // 帶token
        [weakSelf requestDataWithToken:value handle:^(BOOL success) {
            success ? NSLog(@"成功") : NSLog(@"失敗");
            NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
        }];
    }];
}

/** 獲取token請求 */
- (void)requestToken:(void(^)(id value))successBlock{
    NSLog(@"開始請求token");
    [NSThread sleepForTimeInterval:1];
    if (successBlock) {
        successBlock(@"b2a8f8523ab41f8b4b9b2a79ff47c3f1");
    }
}

/** 請求所有數(shù)據(jù) */
- (void)requestDataWithToken: (NSString *)token handle: (void(^)(BOOL success))successBlock {
    dispatch_queue_t queue = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [self requestHeadDataWithToken: token handle:^(id value) { NSLog(@"%@", value); }];
    });
    
    dispatch_async(queue, ^{
        [self requestListDataWithToken:token handle:^(id value) { NSLog(@"%@", value); }];
    });
    
    dispatch_barrier_async(queue, ^{  successBlock(true); });
    
}

/** 頭部數(shù)據(jù)的請求 */
- (void)requestHeadDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
    if (token.length == 0) {
        NSLog(@"沒有token,因為安全性無法請求數(shù)據(jù)");
        return;
    }
    [NSThread sleepForTimeInterval:2];
    if (successBlock) {
        successBlock(@"我是頭,都聽我的");
    }
}
/** 列表數(shù)據(jù)的請求 */
- (void)requestListDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
    if (token.length == 0) {
        NSLog(@"沒有token,因為安全性無法請求數(shù)據(jù)");
        return;
    }
    [NSThread sleepForTimeInterval:1];
    if (successBlock) {
        successBlock(@"我是列表數(shù)據(jù)");
    }
}
@end

7. 調(diào)度組 Group

柵欄函數(shù)類似,也是控制任務(wù)的執(zhí)行順序裁厅。

  • dispatch_group_create 創(chuàng)建組
  • dispatch_group_async 進組任務(wù) (自動管理進組出組)
  • dispatch_group_notify 進組任務(wù)執(zhí)行完畢通知
  • dispatch_group_wait 進組任務(wù)執(zhí)行等待時間
  • dispatch_group_enter 進組

  • dispatch_group_leave 出組
    進組出組需要成對搭配使用

  • 代碼案例

//MARK: -ViewController
@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    __block CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
    
    // 1. 【手動入組和出組】
    [self requestToken:^(id value) {
        dispatch_group_t group = dispatch_group_create();
        dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);

        dispatch_group_enter(group);
        dispatch_async(concurrent, ^{
            [weakSelf requestHeadDataWithToken:value handle:^(id value) {
                NSLog(@"%@",value);
                dispatch_group_leave(group);
            }];
        });

        dispatch_group_enter(group);
        dispatch_async(concurrent, ^{
            [weakSelf requestListDataWithToken:value handle:^(id value) {
                NSLog(@"%@",value);
                dispatch_group_leave(group);
            }];
        });

        dispatch_group_notify(group, concurrent, ^{
            NSLog(@"成功了");
            NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
        });
    }];
    
//    // 2. 【自動入組和出組】
//    [self requestToken:^(id value) {
//        dispatch_group_t group = dispatch_group_create();
//        dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
//
//        dispatch_group_async(group, concurrent, ^{
//            [weakSelf requestHeadDataWithToken:value handle:^(id value) {
//                NSLog(@"%@",value);
//            }];
//        });
//
//        dispatch_group_async(group, concurrent, ^{
//            [weakSelf requestListDataWithToken:value handle:^(id value) {
//                NSLog(@"%@",value);
//            }];
//        });
//
//        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//            NSLog(@"成功了");
//            NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
//        });
//
//    }];
    
//    // 3. 【同步函數(shù) + 自動入組出組】
//    __block NSString * token;
//    dispatch_sync(dispatch_queue_create("ht", DISPATCH_QUEUE_SERIAL), ^{
//        [self requestToken:^(id value) {
//            token = value;
//        }];
//    });
//
//    dispatch_group_t group = dispatch_group_create();
//    dispatch_queue_t concurrent = dispatch_queue_create("ht", DISPATCH_QUEUE_CONCURRENT);
//
//    dispatch_group_async(group, concurrent, ^{
//        [weakSelf requestHeadDataWithToken: token handle:^(id value) {
//            NSLog(@"%@",value);
//        }];
//    });
//
//    dispatch_group_async(group, concurrent, ^{
//        [weakSelf requestListDataWithToken: token handle:^(id value) {
//            NSLog(@"%@",value);
//        }];
//    });
//
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        NSLog(@"成功了");
//        NSLog(@"%f", CFAbsoluteTimeGetCurrent() - time);
//    });
    
}

/** 獲取token請求 */
- (void)requestToken:(void(^)(id value))successBlock{
    NSLog(@"開始請求token");
    [NSThread sleepForTimeInterval:1];
    if (successBlock) {
        successBlock(@"b2a8f8523ab41f8b4b9b2a79ff47c3f1");
    }
}

/** 頭部數(shù)據(jù)的請求 */
- (void)requestHeadDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
    if (token.length == 0) {
        NSLog(@"沒有token,因為安全性無法請求數(shù)據(jù)");
        return;
    }
    [NSThread sleepForTimeInterval:2];
    if (successBlock) {
        successBlock(@"我是頭,都聽我的");
    }
}
/** 列表數(shù)據(jù)的請求 */
- (void)requestListDataWithToken:(NSString *)token handle:(void(^)(id value))successBlock{
    if (token.length == 0) {
        NSLog(@"沒有token,因為安全性無法請求數(shù)據(jù)");
        return;
    }
    [NSThread sleepForTimeInterval:1];
    if (successBlock) {
        successBlock(@"我是列表數(shù)據(jù)");
    }
}
@end

8. GCD單例

  • 單例:
  1. 利用static內(nèi)存中僅一份的特性冰沙,保證了對象的唯一性
  2. 重寫allocWithZone的實現(xiàn)执虹,讓外界使用alloc創(chuàng)建時拓挥,永遠返回的是static聲明的對象
  • 以下是KCImageManger的核心代碼:
#import "KCImageManger.h"
// 保存在常量區(qū)
static id instance;

@implementation KCImageManger


/**
 每次類初始化的時候進行調(diào)用

 1袋励、+load它不遵循那套繼承規(guī)則侥啤。如果某個類本身沒有實現(xiàn)+load方法,那么不管其它各級超類是否實現(xiàn)此方法茬故,系統(tǒng)都不會調(diào)用愿棋。+load方法調(diào)用順序是:SuperClass -->SubClass --> CategaryClass。
 
 3均牢、+initialize是在類或者它的子類接受第一條消息前被調(diào)用糠雨,但是在它的超類接收到initialize之后。也就是說+initialize是以懶加載的方式被調(diào)用的徘跪,如果程序一直沒有給某個類或它的子類發(fā)送消息甘邀,那么這個類的+initialize方法是不會被調(diào)用的。
 
 4垮庐、只有執(zhí)行+initialize的那個線程可以操作類或類實例松邪,其他線程都要阻塞等著+initialize執(zhí)行完。
 5哨查、+initialize  本身類的調(diào)用都會執(zhí)行父類和分類實現(xiàn) initialize方法都會被調(diào)多次
 
 */
+ (void)initialize{
    NSLog(@"父類");
    if (instance == nil) {
        instance = [[self alloc] init];
    }
}
/**
 配合上面 也能進行單利
 */
+ (instancetype)manager{
    return instance;
}

/**
 * 所有為類的對象分配空間的方法逗抑,最終都會調(diào)用到 allovWithZone 方法
 * 下面這樣的操作相當于鎖死 該類的所有初始化方法
 */
+(instancetype)allocWithZone:(struct _NSZone *)zone{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [super allocWithZone:zone];
    });
    return instance;
}

/**
 單利
 */
+(instancetype)shareManager{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

@end
  • 測試代碼:
- (void)onceDemo{
//    KCImageManger *manger1 = [KCImageManger shareManager];
//    KCImageManger *manger2 = [KCImageManger shareManager];
//    KCImageManger *manger3 = [KCImageManger shareManager];
    
//    KCImageManger *manger1 = [[KCImageManger alloc] init];
//    KCImageManger *manger2 = [[KCImageManger alloc] init];
//    KCImageManger *manger3 = [KCImageManger new];
    
    KCImageManger *manger1 = [[KCImageManger alloc] init];
    KCImageManger *manger2 = [KCImageManger manager];
    KCImageManger *manger3 = [KCImageManger manager];
    
    NSLog(@"%@---%@---%@",manger1,manger2,manger3);
}
  • 打印結(jié)果:
image.png

9. 信號量 semaphore

控制GCD最大并發(fā)數(shù)。(同一時刻可進行的信號(任務(wù))最大個數(shù)寒亥。)

  • dispatch_semaphore_create: 創(chuàng)建信號量
  • dispatch_semaphore_wait: 信號量等待
  • dispatch_semaphore_signal: 信號量釋放

加入了信號量的等待dispatch_semaphore_wait后邮府,一定需要配對加入信號量釋放dispatch_semaphore_signal,不然會crash

  • 示例代碼:
- (void)viewDidLoad {
    [super viewDidLoad];
    // 創(chuàng)建全局隊列(并行)
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
   // 設(shè)置信號量
    dispatch_semaphore_t sem = dispatch_semaphore_create(2); // 最多同時執(zhí)行2個任務(wù)
    
    //任務(wù)1
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(1);
        NSLog(@"執(zhí)行任務(wù)1");
        sleep(1);
        NSLog(@"任務(wù)1完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任務(wù)2
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(1);
        NSLog(@"執(zhí)行任務(wù)2");
        sleep(1);
        NSLog(@"任務(wù)2完成");
        dispatch_semaphore_signal(sem);
    });
    
    //任務(wù)3
    dispatch_async(queue, ^{
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        sleep(1);
        NSLog(@"執(zhí)行任務(wù)3");
        sleep(1);
        NSLog(@"任務(wù)3完成");
        dispatch_semaphore_signal(sem);
    });
}

下一節(jié)溉奕,分析 dispatch源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末褂傀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子加勤,更是在濱河造成了極大的恐慌仙辟,老刑警劉巖同波,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異叠国,居然都是意外死亡未檩,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門粟焊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冤狡,“玉大人,你說我怎么就攤上這事吆玖。” “怎么了马篮?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵沾乘,是天一觀的道長。 經(jīng)常有香客問我浑测,道長翅阵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任迁央,我火速辦了婚禮掷匠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岖圈。我一直安慰自己讹语,他們只是感情好,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布蜂科。 她就那樣靜靜地躺著顽决,像睡著了一般。 火紅的嫁衣襯著肌膚如雪导匣。 梳的紋絲不亂的頭發(fā)上才菠,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天,我揣著相機與錄音贡定,去河邊找鬼赋访。 笑死,一個胖子當著我的面吹牛缓待,可吹牛的內(nèi)容都是我干的蚓耽。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼旋炒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了国葬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤接奈,失蹤者是張志新(化名)和其女友劉穎踢涌,沒想到半個月后序宦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡互捌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年潘明,在試婚紗的時候發(fā)現(xiàn)自己被綠了秕噪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡遂填,死狀恐怖吓坚,靈堂內(nèi)的尸體忽然破棺而出灯荧,到底是詐尸還是另有隱情,我是刑警寧澤客税,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布更耻,位于F島的核電站捏膨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏目胡。R本人自食惡果不足惜誉己,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一域蜗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧筑累,春花似錦慢宗、人聲如沸坪蚁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宾舅。三九已至彩倚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蔬蕊,已是汗流浹背岸夯。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工们妥, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留监婶,地道東北人。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓煮盼,卻偏偏與公主長得像带污,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子报破,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355