《Pro Multithreading and Memory Management for iOS and OS X》技術(shù)分享

《Pro Multithreading and Memory Management for iOS and OS X》


image.png

中文版為《Objective-C高級(jí)編程 iOS與OS X多線程和內(nèi)存管理》,原作者是兩位日本人主要講了ARC、Blocks、GCD三個(gè)模塊,總體來(lái)說(shuō)比較深入,作者試圖從原理上來(lái)揭露內(nèi)在的實(shí)現(xiàn)機(jī)制,強(qiáng)烈建議大家有空的話瞅瞅

一、ARC原理

1.1 引用計(jì)數(shù)原理

iOS中說(shuō)到內(nèi)存管理,不管是手動(dòng)還是ARC,都離不開引用計(jì)數(shù),蘋果采用runtime哈希表來(lái)管理引用計(jì)數(shù),如下圖所示


image.png

蘋果在實(shí)現(xiàn)時(shí),凡是使用引用計(jì)數(shù)的方法,包括retainCount、retain、release方法都調(diào)用了同一個(gè)方法,該方法的實(shí)現(xiàn)原理簡(jiǎn)化后如下所示:

int _CFDoExternRefOperation(uintptr_t op, id obj){
    CFBasicHashRef table = 取得對(duì)象的散列表(obj);
    int count;
    switch(op) {
        case OPERATION_retainCount;
            count = CFBasicHashGetCountOfKey(table, obj);
            return count;
        case OPERATION_retain:
            CFBasicHashAddValue(table, obj);
            return obj;
        case OPERATION_release:
            count = CFBasicHashRemoveValue(table, obj);
            return 0 == count;
}

蘋果這種實(shí)現(xiàn)方式的優(yōu)點(diǎn)是:
不用在每個(gè)對(duì)象內(nèi)存塊中考慮引用計(jì)數(shù)所站的內(nèi)存;
引用計(jì)數(shù)表中存儲(chǔ)的有各個(gè)對(duì)象的內(nèi)存地址,可以直接通過(guò)各條引用技術(shù)追蹤到對(duì)應(yīng)的對(duì)象內(nèi)存塊,在調(diào)試的時(shí)候很有用。

二、Block原理

2.1 簡(jiǎn)介

帶有自動(dòng)變量(局部變量)的匿名函數(shù)

匿名函數(shù):即不帶有名稱的函數(shù),C語(yǔ)言不允許這樣的函數(shù)存在

2.2 void (*block)(void)類型解析

先看一個(gè)OC里的block

//main.m
int main(int argc, char * argv[]) {    
    void (^blk)(void) = ^(){
        int i = 0;
    };
    blk();
    return 0;
}

clang -rewrite-objc main.m方法,將上述代碼轉(zhuǎn)成c++源碼main.cpp,關(guān)鍵代碼如下

#ifndef BLOCK_IMPL
#define BLOCK_IMPL
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        int i = 0;
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

/*主函數(shù)入口*/
int main(int argc, char * argv[]) {
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}
  • 在主函數(shù)main入口里,我們發(fā)現(xiàn)主要初始化了__main_block_impl_0結(jié)構(gòu)體,并調(diào)用他的FuncPtr方法
  • __main_block_impl_0如下
    image.png

2.2.1 具體流程分析

首先 main函數(shù)代碼

int main(int argc, char * argv[]) {
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

簡(jiǎn)化代碼后如下

struct __main_block_impl_0 tmp = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp; 
(*blk->impl.FuncPtr)(blk);

1.首先初始化一個(gè)__main_block_impl_0結(jié)構(gòu)體變量,傳入__main_block_func_0是具體函數(shù)實(shí)現(xiàn),__main_block_desc_0_DATA是需要的空間大小
2.將tmp地址賦值給blk指針
3.在blk里找到impl屬性,該屬性有FuncPtr地址,然后傳入 blk的指針作為參數(shù)。從而實(shí)現(xiàn)OC中的blk();這句話

再看下__main_block_impl_0結(jié)構(gòu)體

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

該結(jié)構(gòu)體主要有一個(gè)初始化函數(shù),初始化傳入指向具體實(shí)現(xiàn)的函數(shù)指針fp、記錄大小的描述信息desc,而isa屬性用于指向結(jié)構(gòu)體的具體類型,這里是_NSConcreteStackBlock,說(shuō)明保存在棧中

OC里類實(shí)際上也是對(duì)象,類和類的實(shí)例都有isa指針


image.png

2.3 帶__block變量的void (*block)(void)類型解析

//main.m
int main(int argc, char * argv[]) {
    __block int a = 10;
    
    void (^blk)(void) = ^(){
        a = a + 1;
    };
    blk();
    return 0;
}

轉(zhuǎn)碼后

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a) = (a->__forwarding->a) + 1;
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

2.3.1 具體流程分析

  • __main_block_impl_0多了__Block_byref_a_0的結(jié)構(gòu)體
  • 描述信息__main_block_desc_0多了copy和dispose,這兩個(gè),一個(gè)做復(fù)制,一個(gè)做銷毀
  • __block變量a在轉(zhuǎn)換為c語(yǔ)言后直接轉(zhuǎn)換為一個(gè)__Block_byref_i_0類型的結(jié)構(gòu)體,
  • block外面的那個(gè)a其實(shí)是block里面的i變量通過(guò)a->__forwarding->a來(lái)獲取的。當(dāng)我們?cè)赽lock里面改變i的值的時(shí)候,其實(shí)是間接的通過(guò)a->__forwarding->a來(lái)改變。其中第一個(gè)a是block里面的變量。第二個(gè)a是block外邊的變量。這樣就解釋了為什么block里面改變a的值block外面的i改變的原因。

2.4 block中捕獲外部變量

代碼如下,block中使用外部變量a

int main(int argc, char * argv[]) {
    int a = 10;
    
    void (^blk)(void) = ^(){
        int b = a + 1;        
    };
    blk();
    
    return 0;
}

轉(zhuǎn)碼后的關(guān)鍵代碼

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

        int b = a + 1;
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, char * argv[]) {
    int a = 10;

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}

可以看到__main_block_impl_0里多了個(gè)屬性a,在block具體實(shí)現(xiàn)里__main_block_func_0,通過(guò)__cself->a拿到外部變量a,其他的沒有什么變化

2.5 全局block類型解析

//main.m
void (^blk)(void) = ^(){
    int a = 10;
    a = a + 1;
};

int main(int argc, char * argv[]) {
    blk();
    return 0;
}

轉(zhuǎn)碼后跟void (*block)(void)類型差不多,主要變化在isa指針

struct __blk_block_impl_0 {
  struct __block_impl impl;
  struct __blk_block_desc_0* Desc;
  __blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

可以看到isa類型為_NSConcreteGlobalBlock,主要是出于該block是給全局使用的程序的數(shù)據(jù)區(qū)域

block的存儲(chǔ)域


image.png

三、GCD

3.0

任務(wù)和隊(duì)列

3.0.1 任務(wù)

就是執(zhí)行操作的意思,簡(jiǎn)單來(lái)說(shuō)就是你在線程中執(zhí)行的那段代碼。在 GCD 中是放在 block 中的。執(zhí)行任務(wù)有兩種方式:同步執(zhí)行(sync)和異步執(zhí)行(async)。兩者的主要區(qū)別是:是否等待隊(duì)列的任務(wù)執(zhí)行結(jié)束,以及是否具備開啟新線程的能力

3.0.2 隊(duì)列

這里的隊(duì)列指執(zhí)行任務(wù)的等待隊(duì)列,即用來(lái)存放任務(wù)的隊(duì)列。隊(duì)列是一種特殊的線性表,采用 FIFO(先進(jìn)先出)的原則,即新任務(wù)總是被插入到隊(duì)列的末尾,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開始讀取。


image.png

3.1 Dispatch Queue

3.1.1 原理

Dispatch Queue 對(duì)于我們開發(fā)者來(lái)說(shuō)應(yīng)該是非常熟悉了,運(yùn)用的場(chǎng)景非常之多,但是他的內(nèi)部是如何實(shí)現(xiàn)的呢?

  • 用于管理追加的Block的C語(yǔ)言層實(shí)現(xiàn)的FIFO隊(duì)列
  • Atomic函數(shù)中實(shí)現(xiàn)的用于排他控制的輕量級(jí)信號(hào)
  • 用于管理線程的C語(yǔ)言層實(shí)現(xiàn)的一些容器
    甚至有人會(huì)想,只要努力編寫線程管理的代碼,就根本用不到GCD,是這樣的嗎?

我們先來(lái)回顧一下蘋果的官方說(shuō)明:

通常,應(yīng)用程序中編寫的線程管理用的代碼要在系統(tǒng)級(jí)實(shí)現(xiàn)。

實(shí)際上正如這句話所說(shuō),在系統(tǒng)級(jí)即iOS和OS X的核心XNU內(nèi)核級(jí)上實(shí)現(xiàn),因此無(wú)論編程人員如何努力編寫管理線程的代碼,在性能方面也不可能勝過(guò)XNU內(nèi)核級(jí)所實(shí)現(xiàn)的GCD。

使用GCD要比使用pthreads和NSThread這些一般的多線程編程API更好。并且,如果用GCD就不必編寫為操作線程反復(fù)出現(xiàn)的類似的源代碼(這里被稱為固定源代碼片段),而可以在線程中集中實(shí)現(xiàn)處理內(nèi)容,真的是好處多多。我們盡量多使用GCD或者使用了Cocoa框架GCD的NSOperationQueue類等API。
首先確認(rèn)一下用于實(shí)現(xiàn)Dispatch Queue 而使用的軟件組件。如表所示


image.png

編程人員所使用GCD的API全部包含在libdispatch庫(kù)的C語(yǔ)言函數(shù)。Dispatch Queue通過(guò)結(jié)構(gòu)體和鏈表,被實(shí)現(xiàn)為FIFO隊(duì)列。FIFO隊(duì)列主要是負(fù)責(zé)管理通過(guò)dispatch_async等函數(shù)所追加的一系列Blocks。所以我們可以理解為一旦我們?cè)诔绦蛑杏缮系较伦芳恿艘唤MBlocks,那么排除掉dispatch_after,其內(nèi)部的追加過(guò)程是一個(gè)先進(jìn)先出原則。

但是Block本身并不是直接加入到這個(gè)FIFO隊(duì)列中,而是先加入Dispatch Continuation這一dispatch_continuation_t類型結(jié)構(gòu)體中,然后再進(jìn)入FIFO隊(duì)列。該結(jié)構(gòu)體用于記憶Block所屬的Dispatch Group和其他一些信息,相當(dāng)于一般常說(shuō)的執(zhí)行上下文(execution context)。

Global Dispatch Queue有如下8種:

Global Dispatch Queue (High priority)
Global Dispatch Queue (Default priority)
Global Dispatch Queue (Low priority)
Global Dispatch Queue (Background priority)
Global Dispatch Queue (High overcommit priority)
Global Dispatch Queue (Default overcommit priority)
Global Dispatch Queue (Low overcommit priority)
Global Dispatch Queue (Background overcommit priority)

注意前面四種 和后面四種不同優(yōu)先級(jí)的Queue有一詞之差:Overcommit。其區(qū)別就在于Overcommit Queue不管系統(tǒng)狀態(tài)如何都會(huì)強(qiáng)制生成線程隊(duì)列。

這8種Global Dispatch Queue 各使用一個(gè)pthread_workqueue。GCD初始化時(shí),使用pthread_workqueue_create_np函數(shù)生成pthread_wrokqueue。

pthread_wrokqueue包含在Libc提供的pthreads API中。它通過(guò)系統(tǒng)的bsdthread_register和workq_open函數(shù)調(diào)用,在初始化XNU內(nèi)核的workqueue之后獲取其信息。

XNU內(nèi)核有4種workqueue:

WORKQUEUE_HIGH_PRIOQUEUE
WORKQUEUE_DEFAULT_PRIOQUEUE
WORKQUEUE_LOW_PRIOQUEUE
WORKQUEUE_BG_PRIOQUEUE

以上為4種執(zhí)行優(yōu)先級(jí)的workqueue。該執(zhí)行優(yōu)先級(jí)與Global Dispatch Queue的4種執(zhí)行優(yōu)先級(jí)相同。

下面看一下Dispatch Queue中執(zhí)行Block的過(guò)程。當(dāng)在Global Dispatch Queue 中執(zhí)行Block時(shí),libdispatch 從Global Dispatch Queue自身的FIFO隊(duì)列取出Dispatch Continuation,調(diào)用pthread_workqueue_additem_np函數(shù)。將該Global Dispatch Queue 本身、符合其優(yōu)先級(jí)的workqueue信息以及執(zhí)行Dispatch Continuation的回調(diào)函數(shù)等傳遞給參數(shù)。


image.png

pthread_workqueue_additem_np函數(shù)使用workq_kernreturn系統(tǒng)調(diào)用,通知workqueue增加應(yīng)當(dāng)執(zhí)行的項(xiàng)目。根據(jù)該通知,XNU內(nèi)核基于系統(tǒng)狀態(tài)判斷是否要生成線程。如果是Overcommit優(yōu)先級(jí)的Global Dispatch Queue ,workqueue則始終生成線程。

該線程雖然與iOS和OS X中通常使用的線程大致相同,但是有一部分pthread API不能使用。詳細(xì)信息可以參考蘋果的官方文檔《并發(fā)編程指南》的“Compatibility with POSIX Threads“這一章節(jié)。

另外,因?yàn)閣orkqueue生成的線程在實(shí)現(xiàn)用于workqueue的線程計(jì)劃表中運(yùn)行,他的上下文切換(shift context)與普通的線程有很大的不同。這也是我們使用GCD的原因。

workqueue的線程執(zhí)行pthread_workqueue函數(shù),該函數(shù)調(diào)用libdispatch的回調(diào)函數(shù)。在該回調(diào)函數(shù)中執(zhí)行加入到Global Dispatch Queue中的下一個(gè)Block。

以上就是Dispatch Queue執(zhí)行的大概過(guò)程。

3.2 dispatch_queue_create

3.2.1 創(chuàng)建并行隊(duì)列

- (void)queue1{
    dispatch_queue_t queue = dispatch_queue_create("com.will.queue", DISPATCH_QUEUE_CONCURRENT);
    for (NSInteger i = 0; i < 10; i ++) {
        dispatch_async(queue, ^{
            NSLog(@"testConcurrent %ld %@",i,[NSThread currentThread]);
        });
    }
}

3.2.2 創(chuàng)建串行隊(duì)列

- (void)queue2{
    dispatch_queue_t queue = dispatch_queue_create("com.will.www", DISPATCH_QUEUE_SERIAL);
    for (NSInteger i = 0; i < 10; i ++) {
        dispatch_async(queue, ^{
            NSLog(@"testConcurrent %ld %@",i,[NSThread currentThread]);
        });
    }    
}

3.3 dispatch_set_target_queue

用法1:指定優(yōu)先級(jí)

dispatch_set_target_queue(queue1,queue2),queue1的優(yōu)先級(jí)將與queue2相同

- (void)queue3{
    dispatch_queue_t serialQueue = dispatch_queue_create("com.will.www",NULL);
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0);

    dispatch_set_target_queue(serialQueue, globalQueue);
    // 第一個(gè)參數(shù)為要設(shè)置優(yōu)先級(jí)的queue,第二個(gè)參數(shù)是參照物,既將第一個(gè)queue的優(yōu)先級(jí)和第二個(gè)queue的優(yōu)先級(jí)設(shè)置一樣。
    
    dispatch_async(serialQueue, ^{
        NSLog(@"我優(yōu)先級(jí)低,先讓讓");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"我優(yōu)先級(jí)高,閃開");
    });
}

打印結(jié)果:
2018-10-08 17:17:35.666835+0800 Demo[7412:306033] 我優(yōu)先級(jí)高,閃開
2018-10-08 17:17:35.666972+0800 Demo[7412:306032] 我優(yōu)先級(jí)低,先讓讓

附注:dispatch_queue_create創(chuàng)建的隊(duì)列優(yōu)先級(jí)與Global Dispatch queue相同

用法2:將多個(gè)隊(duì)列指向一個(gè)隊(duì)列

- (void)queue4{
    //1.創(chuàng)建目標(biāo)隊(duì)列
    dispatch_queue_t targetQueue = dispatch_queue_create("com.will.www", DISPATCH_QUEUE_SERIAL);
    
    //2.創(chuàng)建3個(gè)串行隊(duì)列
    dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL);
    
    //3.將3個(gè)串行隊(duì)列分別添加到目標(biāo)隊(duì)列
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);
    dispatch_set_target_queue(queue3, targetQueue);
    
    
    dispatch_async(queue1, ^{
        NSLog(@"1 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"1 out");
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"2 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"2 out");
    });
    dispatch_async(queue3, ^{
        NSLog(@"3 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"3 out");
    });
}

打印結(jié)果
2018-10-08 17:26:28.748992+0800 Demo[7566:312436] 1 in
2018-10-08 17:26:31.754276+0800 Demo[7566:312436] 1 out
2018-10-08 17:26:31.754540+0800 Demo[7566:312436] 2 in
2018-10-08 17:26:33.757826+0800 Demo[7566:312436] 2 out
2018-10-08 17:26:33.758168+0800 Demo[7566:312436] 3 in
2018-10-08 17:26:34.760729+0800 Demo[7566:312436] 3 out

注意:不可過(guò)多的使用串行隊(duì)列,以免消耗大量?jī)?nèi)存,這里我們只是因?yàn)槎鄠€(gè)線程共同使用了同一個(gè)資源,而使用了串行隊(duì)列

3.4 Dispatch Group

3.4.1 網(wǎng)絡(luò)請(qǐng)求有先后順序

經(jīng)常遇到一種情況,在請(qǐng)求了接口1拿到數(shù)據(jù)的某個(gè)id,才能去接下來(lái)請(qǐng)求接口2,這里可以考慮使用Dispatch Group,代碼如下

- (void)queue5{
    __block NSString *requestID = @"";
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"接口1請(qǐng)求開始");
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        sleep(3); //這里線程睡眠1秒鐘,模擬異步請(qǐng)求
        NSLog(@"接口1拿到數(shù)據(jù)");
        requestID = @"123";
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"接口2請(qǐng)求開始,依據(jù)id:%@", requestID);
        sleep(2);
        NSLog(@"接口2請(qǐng)求結(jié)束");
        NSLog(@"group finished");
    });
}

3.4.2 網(wǎng)絡(luò)請(qǐng)求沒有先后順序,但是全部請(qǐng)求完再做相應(yīng)的處理

- (void)batchRequestWithCompletion:(void (^)(NSError* error))completion{
    __block NSError *configError = nil;
    __block NSError *preferenceError = nil;

    dispatch_group_t serviceGroup = dispatch_group_create();
    
    // 請(qǐng)求第一個(gè)接口
    dispatch_group_enter(serviceGroup);
    [self.service1 startWithCompletion:^(ConfigResponse *results, NSError* error){
        // 處理結(jié)果
        configError = error;
        dispatch_group_leave(serviceGroup);
    }];
    
    // 請(qǐng)求第一個(gè)接口
    dispatch_group_enter(serviceGroup);
    [self.service2 startWithCompletion:^(PreferenceResponse *results, NSError* error){
        // 處理結(jié)果
        preferenceError = error;
        dispatch_group_leave(serviceGroup);
    }];
    
    dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
        NSError *overallError = nil;
        if (configError || preferenceError)
        {
            // 處理錯(cuò)誤信息
            overallError = configError ?: preferenceError;
        }
        // 回調(diào)結(jié)果
        completion(overallError);
    });
}

和內(nèi)存管理的引用計(jì)數(shù)類似,我們可以認(rèn)為group也持有一個(gè)整形變量(只是假設(shè)),當(dāng)調(diào)用enter時(shí)計(jì)數(shù)加1,調(diào)用leave時(shí)計(jì)數(shù)減1,當(dāng)計(jì)數(shù)為0時(shí)會(huì)調(diào)用dispatch_group_notify并且dispatch_group_wait會(huì)停止等待

3.5 dispatch_barrier_async

在并行隊(duì)列中,為了保持某些任務(wù)的順序,需要等待一些任務(wù)完成后才能繼續(xù)進(jìn)行,使用 barrier 來(lái)等待之前任務(wù)完成,避免數(shù)據(jù)競(jìng)爭(zhēng)等問(wèn)題。

- (void)queue6{
    dispatch_queue_t queue = dispatch_queue_create("will's thread", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
}

打印結(jié)果
2018-10-08 18:10:05.872148+0800 Demo[8425:346989] ----1-----<NSThread: 0x600000467680>{number = 3, name = (null)}
2018-10-08 18:10:05.872192+0800 Demo[8425:346986] ----2-----<NSThread: 0x60400026f940>{number = 4, name = (null)}
2018-10-08 18:10:05.872786+0800 Demo[8425:346986] ----barrier-----<NSThread: 0x60400026f940>{number = 4, name = (null)}
2018-10-08 18:10:05.874315+0800 Demo[8425:346989] ----4-----<NSThread: 0x600000467680>{number = 3, name = (null)}
2018-10-08 18:10:05.874315+0800 Demo[8425:346986] ----3-----<NSThread: 0x60400026f940>{number = 4, name = (null)}

dispatch_barrier_sync與dispatch_barrier_async,不同的地方在于前者必須等待柵欄里的block執(zhí)行完才返回,后者則是立即返回

3.6 dispatch_apply

相當(dāng)于給隊(duì)列加了for循環(huán)

- (void)queue7{
    dispatch_queue_t queue = dispatch_queue_create("will's thread", DISPATCH_QUEUE_SERIAL);
    dispatch_apply(10, queue, ^(size_t index) {
         NSLog(@"%zu", index);
    });
    NSLog(@"finished");
}

打印結(jié)果
2018-10-08 18:22:52.854529+0800 Demo[8765:357453] 0
2018-10-08 18:22:52.855043+0800 Demo[8765:357453] 1
2018-10-08 18:22:52.855147+0800 Demo[8765:357453] 2
2018-10-08 18:22:52.855257+0800 Demo[8765:357453] 3
2018-10-08 18:22:52.855367+0800 Demo[8765:357453] 4
2018-10-08 18:22:52.855457+0800 Demo[8765:357453] 5
2018-10-08 18:22:52.855529+0800 Demo[8765:357453] 6
2018-10-08 18:22:52.855599+0800 Demo[8765:357453] 7
2018-10-08 18:22:52.855871+0800 Demo[8765:357453] 8
2018-10-08 18:22:52.856033+0800 Demo[8765:357453] 9
2018-10-08 18:22:52.856233+0800 Demo[8765:357453] finished

若隊(duì)列是并發(fā)的,則執(zhí)行順序就是隨機(jī)的

dispatch_queue_t queue = dispatch_queue_create("will's thread", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zu", index);
});
NSLog(@"finished");
    
2018-10-08 18:23:51.791987+0800 Demo[8849:360039] 0
2018-10-08 18:23:51.792015+0800 Demo[8849:360081] 1
2018-10-08 18:23:51.792032+0800 Demo[8849:360080] 2
2018-10-08 18:23:51.792039+0800 Demo[8849:360078] 3
2018-10-08 18:23:51.792155+0800 Demo[8849:360039] 4
2018-10-08 18:23:51.792191+0800 Demo[8849:360081] 5
2018-10-08 18:23:51.792240+0800 Demo[8849:360078] 6
2018-10-08 18:23:51.792250+0800 Demo[8849:360080] 7
2018-10-08 18:23:51.792252+0800 Demo[8849:360039] 8
2018-10-08 18:23:51.792467+0800 Demo[8849:360081] 9
2018-10-08 18:23:51.793167+0800 Demo[8849:360039] finished

3.7 dispatch_suspend/dispatch_resume

- (void)queue8{
    dispatch_queue_t queue=dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
    
    dispatch_suspend(queue);
    NSLog(@"掛起");
    
    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
    
    sleep(1);
    NSLog(@"繼續(xù)");
    dispatch_resume(queue);
    
    dispatch_async(queue, ^{
        NSLog(@"----5-----%@", [NSThread currentThread]);
    });
}

打印如下:
2018-10-08 19:17:55.933321+0800 Demo[9304:377440] 掛起
2018-10-08 19:17:55.933528+0800 Demo[9304:377550] ----1-----<NSThread: 0x600000265f00>{number = 4, name = (null)}
2018-10-08 19:17:55.933534+0800 Demo[9304:377557] ----2-----<NSThread: 0x604000463400>{number = 3, name = (null)}
2018-10-08 19:17:56.934891+0800 Demo[9304:377440] 繼續(xù)
2018-10-08 19:17:56.935219+0800 Demo[9304:377557] ----3-----<NSThread: 0x604000463400>{number = 3, name = (null)}
2018-10-08 19:17:56.935246+0800 Demo[9304:377550] ----4-----<NSThread: 0x600000265f00>{number = 4, name = (null)}
2018-10-08 19:17:56.935247+0800 Demo[9304:377491] ----5-----<NSThread: 0x60400046d340>{number = 5, name = (null)}

suspend不會(huì)暫停隊(duì)列里已經(jīng)執(zhí)行的任務(wù),比如上面的任務(wù)1、任務(wù)2,只要在suspend后面的任務(wù)才有效

3.8 Dispatch Semaphore

- (void)queue9{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任務(wù)1
        [NSThread sleepForTimeInterval:2];              // 模擬耗時(shí)操作
        NSLog(@"開始任務(wù)---%@",[NSThread currentThread]);      // 打印當(dāng)前線程
        
        number = 100;
        
        NSLog(@"發(fā)送信號(hào)---%@",[NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);        
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"收到信號(hào),number = %d",number);
}

打印結(jié)果
2018-10-08 19:27:56.010178+0800 Demo[9552:386731] 開始任務(wù)---<NSThread: 0x600000278940>{number = 3, name = (null)}
2018-10-08 19:27:56.010488+0800 Demo[9552:386731] 發(fā)送信號(hào)---<NSThread: 0x600000278940>{number = 3, name = (null)}
2018-10-08 19:27:56.010868+0800 Demo[9552:386687] 收到信號(hào),number = 100

dispatch_semaphore_create(0)創(chuàng)建一個(gè)semaphore,此時(shí)信號(hào)量為0, dispatch_semaphore_wait會(huì)使信號(hào)量-1,當(dāng)信號(hào)量<0,就會(huì)進(jìn)入等待,dispatch_semaphore_signal會(huì)發(fā)送一個(gè)信號(hào),使信號(hào)量+1

3.9 Dispatch I/O

可用于異步讀取本地大的文件

- (void)queue10{
    NSLog(@"開始");
    NSString *desktop =@"/Users/will/Documents/Q3技術(shù)分享";
    
    NSString *path = [desktop stringByAppendingPathComponent:@"image.jpeg"];
    
    dispatch_queue_t queue =dispatch_queue_create("com.will.read",DISPATCH_QUEUE_CONCURRENT);//當(dāng)設(shè)置為并行隊(duì)列時(shí)在讀取文件時(shí)實(shí)際還是串行
    
    dispatch_fd_t fd =open(path.UTF8String,O_RDONLY, 0);
    
    dispatch_io_t io =dispatch_io_create(DISPATCH_IO_STREAM, fd, queue, ^(int error) {
        close(fd);
    });
    
    size_t water =1024*1024;
    
    dispatch_io_set_low_water(io, water);
    
    dispatch_io_set_high_water(io, water);
    
    long long fileSize = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil].fileSize;
    
    NSMutableData *totalData = [[NSMutableData alloc] init];
    
    
    __weak __typeof(self)weakSelf = self;
    dispatch_io_read(io,0, fileSize, 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) {
            UIImage *image = [UIImage imageWithData:totalData.copy];
            __strong __typeof(weakSelf)strongSelf = weakSelf;
            
            __weak __typeof(self)weakSelf = strongSelf;
            dispatch_async(dispatch_get_main_queue(), ^{
                __strong __typeof(weakSelf)strongSelf = weakSelf;
                strongSelf.coverImageView.image = image;
                NSLog(@"結(jié)束");
            });
        }
    });
}
  • 創(chuàng)建一個(gè)調(diào)度I / O通道
dispatch_io_t dispatch_io_create( dispatch_io_type_t type, dispatch_fd_t fd, dispatch_queue_t queue, void (^cleanup_handler)(int error));

define DISPATCH_IO_STREAM 0
讀寫操作按順序依次順序進(jìn)行。在讀或?qū)戦_始時(shí),操作總是在文件指針位置讀或?qū)憯?shù)據(jù)。讀和寫操作可以在同一個(gè)信道上同時(shí)進(jìn)行。
define DISPATCH_IO_RANDOM 1
隨機(jī)訪問(wèn)文件。讀和寫操作可以同時(shí)執(zhí)行這種類型的通道,文件描述符必須是可尋址的。
fd 文件描述符
queue The dispatch queue
cleanup_handler 發(fā)生錯(cuò)誤時(shí)用來(lái)執(zhí)行處理的 Block

  • 設(shè)置一次讀取的最大字節(jié)
void dispatch_io_set_high_water( dispatch_io_t channel, size_t high_water);
  • 設(shè)置一次讀取的最小字節(jié)
void dispatch_io_set_low_water( dispatch_io_t channel, size_t low_water);

3.10 Dispatch source

- (void)queue11{
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_global_queue(0, 0));
    
    dispatch_source_set_event_handler(source, ^{
        NSLog(@"監(jiān)聽函數(shù):%lu",dispatch_source_get_data(source));
        dispatch_async(dispatch_get_main_queue(), ^{
            
            //更新UI
            NSLog(@"更新UI");
        });
    });
    
    dispatch_resume(source);
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //網(wǎng)絡(luò)請(qǐng)求
        NSLog(@"網(wǎng)絡(luò)請(qǐng)求");
        dispatch_source_merge_data(source, 1); //通知隊(duì)列
    });
}

打印結(jié)果
2018-10-08 20:29:48.254294+0800 Demo[11127:442148] 網(wǎng)絡(luò)請(qǐng)求
2018-10-08 20:29:48.254721+0800 Demo[11127:442150] 監(jiān)聽函數(shù):1
2018-10-08 20:29:53.527990+0800 Demo[11127:442088] 更新UI

注意:這里dispatch source創(chuàng)建時(shí)默認(rèn)處于暫停狀態(tài),在分派處理程序之前必須先恢復(fù)

Dispatch Source可處理的所有事件


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容