《Pro Multithreading and Memory Management for iOS and OS X》技术分享

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


image.png

中文版为《Objective-C高级编程 iOS与OS X多线程和内存管理》,原作者是两位日本人主要讲了ARC、Blocks、GCD三个模块,总体来说比较深入,作者试图从原理上来揭露内在的实现机制,强烈建议大家有空的话瞅瞅

一、ARC原理

1.1 引用计数原理

iOS中说到内存管理,不管是手动还是ARC,都离不开引用计数,苹果采用runtime哈希表来管理引用计数,如下图所示


image.png

苹果在实现时,凡是使用引用计数的方法,包括retainCount、retain、release方法都调用了同一个方法,该方法的实现原理简化后如下所示:

int _CFDoExternRefOperation(uintptr_t op, id obj){
    CFBasicHashRef table = 取得对象的散列表(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;
}

苹果这种实现方式的优点是:
不用在每个对象内存块中考虑引用计数所站的内存;
引用计数表中存储的有各个对象的内存地址,可以直接通过各条引用技术追踪到对应的对象内存块,在调试的时候很有用。

二、Block原理

2.1 简介

带有自动变量(局部变量)的匿名函数

匿名函数:即不带有名称的函数,C语言不允许这样的函数存在

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

先看一个OC里的block

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

clang -rewrite-objc main.m方法,将上述代码转成c++源码main.cpp,关键代码如下

#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)};

/*主函数入口*/
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;
}
  • 在主函数main入口里,我们发现主要初始化了__main_block_impl_0结构体,并调用他的FuncPtr方法
  • __main_block_impl_0如下
    image.png

2.2.1 具体流程分析

首先 main函数代码

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;
}

简化代码后如下

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.首先初始化一个__main_block_impl_0结构体变量,传入__main_block_func_0是具体函数实现,__main_block_desc_0_DATA是需要的空间大小
2.将tmp地址赋值给blk指针
3.在blk里找到impl属性,该属性有FuncPtr地址,然后传入 blk的指针作为参数。从而实现OC中的blk();这句话

再看下__main_block_impl_0结构体

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;
  }

该结构体主要有一个初始化函数,初始化传入指向具体实现的函数指针fp、记录大小的描述信息desc,而isa属性用于指向结构体的具体类型,这里是_NSConcreteStackBlock,说明保存在栈中

OC里类实际上也是对象,类和类的实例都有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;
}

转码后

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的结构体
  • 描述信息__main_block_desc_0多了copy和dispose,这两个,一个做复制,一个做销毁
  • __block变量a在转换为c语言后直接转换为一个__Block_byref_i_0类型的结构体,
  • block外面的那个a其实是block里面的i变量通过a->__forwarding->a来获取的。当我们在block里面改变i的值的时候,其实是间接的通过a->__forwarding->a来改变。其中第一个a是block里面的变量。第二个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;
}

转码后的关键代码

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里多了个属性a,在block具体实现里__main_block_func_0,通过__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;
}

转码后跟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是给全局使用的程序的数据区域

block的存储域


image.png

三、GCD

3.0

任务和队列

3.0.1 任务

就是执行操作的意思,简单来说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:同步执行(sync)和异步执行(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力

3.0.2 队列

这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。


image.png

3.1 Dispatch Queue

3.1.1 原理

Dispatch Queue 对于我们开发者来说应该是非常熟悉了,运用的场景非常之多,但是他的内部是如何实现的呢?

  • 用于管理追加的Block的C语言层实现的FIFO队列
  • Atomic函数中实现的用于排他控制的轻量级信号
  • 用于管理线程的C语言层实现的一些容器
    甚至有人会想,只要努力编写线程管理的代码,就根本用不到GCD,是这样的吗?

我们先来回顾一下苹果的官方说明:

通常,应用程序中编写的线程管理用的代码要在系统级实现。

实际上正如这句话所说,在系统级即iOS和OS X的核心XNU内核级上实现,因此无论编程人员如何努力编写管理线程的代码,在性能方面也不可能胜过XNU内核级所实现的GCD。

使用GCD要比使用pthreads和NSThread这些一般的多线程编程API更好。并且,如果用GCD就不必编写为操作线程反复出现的类似的源代码(这里被称为固定源代码片段),而可以在线程中集中实现处理内容,真的是好处多多。我们尽量多使用GCD或者使用了Cocoa框架GCD的NSOperationQueue类等API。
首先确认一下用于实现Dispatch Queue 而使用的软件组件。如表所示


image.png

编程人员所使用GCD的API全部包含在libdispatch库的C语言函数。Dispatch Queue通过结构体和链表,被实现为FIFO队列。FIFO队列主要是负责管理通过dispatch_async等函数所追加的一系列Blocks。所以我们可以理解为一旦我们在程序中由上到下追加了一组Blocks,那么排除掉dispatch_after,其内部的追加过程是一个先进先出原则。

但是Block本身并不是直接加入到这个FIFO队列中,而是先加入Dispatch Continuation这一dispatch_continuation_t类型结构体中,然后再进入FIFO队列。该结构体用于记忆Block所属的Dispatch Group和其他一些信息,相当于一般常说的执行上下文(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)

注意前面四种 和后面四种不同优先级的Queue有一词之差:Overcommit。其区别就在于Overcommit Queue不管系统状态如何都会强制生成线程队列。

这8种Global Dispatch Queue 各使用一个pthread_workqueue。GCD初始化时,使用pthread_workqueue_create_np函数生成pthread_wrokqueue。

pthread_wrokqueue包含在Libc提供的pthreads API中。它通过系统的bsdthread_register和workq_open函数调用,在初始化XNU内核的workqueue之后获取其信息。

XNU内核有4种workqueue:

WORKQUEUE_HIGH_PRIOQUEUE
WORKQUEUE_DEFAULT_PRIOQUEUE
WORKQUEUE_LOW_PRIOQUEUE
WORKQUEUE_BG_PRIOQUEUE

以上为4种执行优先级的workqueue。该执行优先级与Global Dispatch Queue的4种执行优先级相同。

下面看一下Dispatch Queue中执行Block的过程。当在Global Dispatch Queue 中执行Block时,libdispatch 从Global Dispatch Queue自身的FIFO队列取出Dispatch Continuation,调用pthread_workqueue_additem_np函数。将该Global Dispatch Queue 本身、符合其优先级的workqueue信息以及执行Dispatch Continuation的回调函数等传递给参数。


image.png

pthread_workqueue_additem_np函数使用workq_kernreturn系统调用,通知workqueue增加应当执行的项目。根据该通知,XNU内核基于系统状态判断是否要生成线程。如果是Overcommit优先级的Global Dispatch Queue ,workqueue则始终生成线程。

该线程虽然与iOS和OS X中通常使用的线程大致相同,但是有一部分pthread API不能使用。详细信息可以参考苹果的官方文档《并发编程指南》的“Compatibility with POSIX Threads“这一章节。

另外,因为workqueue生成的线程在实现用于workqueue的线程计划表中运行,他的上下文切换(shift context)与普通的线程有很大的不同。这也是我们使用GCD的原因。

workqueue的线程执行pthread_workqueue函数,该函数调用libdispatch的回调函数。在该回调函数中执行加入到Global Dispatch Queue中的下一个Block。

以上就是Dispatch Queue执行的大概过程。

3.2 dispatch_queue_create

3.2.1 创建并行队列

- (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 创建串行队列

- (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:指定优先级

dispatch_set_target_queue(queue1,queue2),queue1的优先级将与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);
    // 第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。
    
    dispatch_async(serialQueue, ^{
        NSLog(@"我优先级低,先让让");
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"我优先级高,闪开");
    });
}

打印结果:
2018-10-08 17:17:35.666835+0800 Demo[7412:306033] 我优先级高,闪开
2018-10-08 17:17:35.666972+0800 Demo[7412:306032] 我优先级低,先让让

附注:dispatch_queue_create创建的队列优先级与Global Dispatch queue相同

用法2:将多个队列指向一个队列

- (void)queue4{
    //1.创建目标队列
    dispatch_queue_t targetQueue = dispatch_queue_create("com.will.www", DISPATCH_QUEUE_SERIAL);
    
    //2.创建3个串行队列
    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个串行队列分别添加到目标队列
    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");
    });
}

打印结果
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

注意:不可过多的使用串行队列,以免消耗大量内存,这里我们只是因为多个线程共同使用了同一个资源,而使用了串行队列

3.4 Dispatch Group

3.4.1 网络请求有先后顺序

经常遇到一种情况,在请求了接口1拿到数据的某个id,才能去接下来请求接口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请求开始");
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        sleep(3); //这里线程睡眠1秒钟,模拟异步请求
        NSLog(@"接口1拿到数据");
        requestID = @"123";
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"接口2请求开始,依据id:%@", requestID);
        sleep(2);
        NSLog(@"接口2请求结束");
        NSLog(@"group finished");
    });
}

3.4.2 网络请求没有先后顺序,但是全部请求完再做相应的处理

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

    dispatch_group_t serviceGroup = dispatch_group_create();
    
    // 请求第一个接口
    dispatch_group_enter(serviceGroup);
    [self.service1 startWithCompletion:^(ConfigResponse *results, NSError* error){
        // 处理结果
        configError = error;
        dispatch_group_leave(serviceGroup);
    }];
    
    // 请求第一个接口
    dispatch_group_enter(serviceGroup);
    [self.service2 startWithCompletion:^(PreferenceResponse *results, NSError* error){
        // 处理结果
        preferenceError = error;
        dispatch_group_leave(serviceGroup);
    }];
    
    dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
        NSError *overallError = nil;
        if (configError || preferenceError)
        {
            // 处理错误信息
            overallError = configError ?: preferenceError;
        }
        // 回调结果
        completion(overallError);
    });
}

和内存管理的引用计数类似,我们可以认为group也持有一个整形变量(只是假设),当调用enter时计数加1,调用leave时计数减1,当计数为0时会调用dispatch_group_notify并且dispatch_group_wait会停止等待

3.5 dispatch_barrier_async

在并行队列中,为了保持某些任务的顺序,需要等待一些任务完成后才能继续进行,使用 barrier 来等待之前任务完成,避免数据竞争等问题。

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

打印结果
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执行完才返回,后者则是立即返回

3.6 dispatch_apply

相当于给队列加了for循环

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

打印结果
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

若队列是并发的,则执行顺序就是随机的

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(@"继续");
    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] 继续
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不会暂停队列里已经执行的任务,比如上面的任务1、任务2,只要在suspend后面的任务才有效

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, ^{
        // 追加任务1
        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
        NSLog(@"开始任务---%@",[NSThread currentThread]);      // 打印当前线程
        
        number = 100;
        
        NSLog(@"发送信号---%@",[NSThread currentThread]);
        dispatch_semaphore_signal(semaphore);        
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"收到信号,number = %d",number);
}

打印结果
2018-10-08 19:27:56.010178+0800 Demo[9552:386731] 开始任务---<NSThread: 0x600000278940>{number = 3, name = (null)}
2018-10-08 19:27:56.010488+0800 Demo[9552:386731] 发送信号---<NSThread: 0x600000278940>{number = 3, name = (null)}
2018-10-08 19:27:56.010868+0800 Demo[9552:386687] 收到信号,number = 100

dispatch_semaphore_create(0)创建一个semaphore,此时信号量为0, dispatch_semaphore_wait会使信号量-1,当信号量<0,就会进入等待,dispatch_semaphore_signal会发送一个信号,使信号量+1

3.9 Dispatch I/O

可用于异步读取本地大的文件

- (void)queue10{
    NSLog(@"开始");
    NSString *desktop =@"/Users/will/Documents/Q3技术分享";
    
    NSString *path = [desktop stringByAppendingPathComponent:@"image.jpeg"];
    
    dispatch_queue_t queue =dispatch_queue_create("com.will.read",DISPATCH_QUEUE_CONCURRENT);//当设置为并行队列时在读取文件时实际还是串行
    
    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(@"结束");
            });
        }
    });
}
  • 创建一个调度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
读写操作按顺序依次顺序进行。在读或写开始时,操作总是在文件指针位置读或写数据。读和写操作可以在同一个信道上同时进行。
define DISPATCH_IO_RANDOM 1
随机访问文件。读和写操作可以同时执行这种类型的通道,文件描述符必须是可寻址的。
fd 文件描述符
queue The dispatch queue
cleanup_handler 发生错误时用来执行处理的 Block

  • 设置一次读取的最大字节
void dispatch_io_set_high_water( dispatch_io_t channel, size_t high_water);
  • 设置一次读取的最小字节
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(@"监听函数:%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), ^{
        //网络请求
        NSLog(@"网络请求");
        dispatch_source_merge_data(source, 1); //通知队列
    });
}

打印结果
2018-10-08 20:29:48.254294+0800 Demo[11127:442148] 网络请求
2018-10-08 20:29:48.254721+0800 Demo[11127:442150] 监听函数:1
2018-10-08 20:29:53.527990+0800 Demo[11127:442088] 更新UI

注意:这里dispatch source创建时默认处于暂停状态,在分派处理程序之前必须先恢复

Dispatch Source可处理的所有事件


image.png

推荐阅读更多精彩内容