4

iOS中GCD的实现与总结

 2 years ago
source link: https://yaoguais.github.io/article/ios/gcd.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

iOS中GCD的实现与总结

GCD全称"Grand Central Dispatch", 中央调度的意思, 是iOS中标准异步处理的技术.

为什么会产生GCD呢, 这个跟其他语言类比下, 在Java中多使用线程, Erlang中使用协程, 其中协程比线程更加轻量更加优雅, 而iOS中的GCD就可以类比为协程这样让代码更加优雅的东西.

本篇内容是《Objective-C高级编程 iOS与OS X多线程和内存管理》的最后一部分, 也代表对这本书整理完毕了.

  1. GCD的基础使用
  2. GCD的分类
  3. GCD相关的函数及其使用
    • dispatch_queue_create
    • dispatch_get_main_queue
    • dispatch_get_global_queue
    • dispatch_set_target_queue
    • dispatch_after
    • dispatch_group
    • dispatch_barrier
    • dispatch_sync
    • dispatch_apply
    • dispatch_suspend/dispatch_resume
    • dispatch_semaphore
    • dispatch_once
    • dispatch I/O
  4. GCD的实现

GCD的基础使用

GCD的代码比较优雅, 应用简单, 也很好理解, 下面是一段示例代码:

dispatch_async(queue, ^{
    // 后台长时间的处理
    dispatch_async(dispatch_get_main_queue(), ^{
        // 主线程(UI线程)处理
    });
});

其中queue是"dispatch_queue_t"类型的变量, block是执行的代码块.

这段代码也可以使用NSObject的两个静态方法实现,

[NSObject performSelectorOnMainThread:<#(SEL)aSelector#> withObject:<#(id)arg#> waitUntilDone:<#(BOOL)wait#>];
[NSObject performSelectorInBackground:<#(SEL)aSelector#> withObject:<#(id)arg#>];

但是代码就会变得有点散杂乱了.

GCD的分类

GCD的Queue总共分为两大类:

  1. Serial Dispatch Queue
  2. Concurrent Dispatch Queue

"Serial Dispatch Queue"是一个串行的队列, 添加到这种queue的block会依次执行. 但是也可以创建多个不同标识的这种queue, 它们内部的block依然是依次执行的, 但是这些queue之间是同时执行的. 需要注意的是, 一个serial类型的queue会对应一个线程, 创建大量的线程会损耗系统的性能.

"Concurrent Dispatch Queue"是"共享"多个串行的队列的结构, 每个队列跟serial类型的queue并无差别, 而队列的个数一般与操作系统的CPU个数相关.

上面"共享"的意思是不管你创建多少了concurrent类型的queue, 它们能使用的队列个数都是确定的一个值.

创建方式也很简单:

dispatch_queue_t serialQueue = dispatch_queue_create("com.jegarn.serial_dispatch_queue", nil);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jegarn.concurrent_dispatch_queue", DISPATCH_QUEUE_CONCURRENT);

第一个参数是string类型的唯一标识, 第二个是Queue的类型标识.

需要注意的是"dispatch_queue_t"是C语言的结构体, 所以其并不在ARC的管理下, 所以在不使用的时候, 需要调用release方法释放.

dispatch_release(serialQueue);

当然也可以增加其引用计数, 这与MRC管理内存的方式一致.

dispatch_retain(concurrentQueue);

上面我们有提到"dispatch_get_main_queue(void)"函数, 其实这是系统为我们提供获取主UI线程的queue的方法, 因为主UI线程的执行都是串行的, 那么获取也就是一个serial类型的queue.

系统还为我们提供一个函数"dispatch_get_global_queue(,)", 这是为我们提供了一个全局的concurrent类型的queue.

用这两个函数的好处就是我们不需要做额外的内存管理了, 因为都是系统帮我们创建的, 其管理交给系统就好了.

"dispatch_get_global_queue(,)"函数有两个参数, 第一个参数是设置放到这个queue里面的block的执行优先级, 第二个参数是一个flag, 传入0即可.

其优先级的定义如下:

#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN

根据我们上面对concurrent类型的queue的解释, 这个优先级并不一定就控制了block被执行的先后顺序, 它应该是一个建议的参数.

GCD相关的函数及其使用

dispatch_queue_create

这个函数我们已经在上面详细的讲解了, 其函数原型为:

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

dispatch_get_main_queue

这个函数我们已经在上面详细的讲解了, 其函数原型为:

dispatch_queue_t dispatch_get_main_queue(void);

dispatch_get_global_queue

这个函数我们已经在上面详细的讲解了, 其函数原型为:

dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

dispatch_set_target_queue

其函数原型为:

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

其含义类似于把一个queue当成block放到另一个queue里面去.

另一个就是通过"dispatch_queue_create()"获取的queue默认优先级是"DISPATCH_QUEUE_PRIORITY_DEFAULT", 所以我们还可以通过这个函数改变其优先级.

dispatch_after

dispatch_after可以设置一个block在指定的时间执行

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

比如下面代码我们设置一个block在3秒后执行

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^ { printf("hello\n"); });

dispatch_group

dispatch_group可以设置在多个block都执行完毕后回调一个block, 其用法的示例代码如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{ printf("1"); });
dispatch_group_async(group, queue, ^{ printf("2"); });
dispatch_group_async(group, queue, ^{ printf("3"); });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ printf("all done!"); });
dispatch_release(group);

我们也可以使用函数阻塞的等待添加的block执行, 其在等待了执行的时间之后, 如果返回0则代表所有block都执行完毕, 否则即有block仍在执行中.

其函数原型为:

long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

dispatch_barrier

这个函数的原型为:

void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

这个函数一般作用于concurrent类型的queue上, 用于将调用这个函数前后的block分为两组, 前面的block组并发执行完毕后, 才会单独执行"dispatch_barrier_async"设置的block, 然后再并发执行后面的block组.

这个也能通过group实现, 但是barrier提供了更简单的实现.

dispatch_sync

其函数原型为:

void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

"dispatch_sync"从名字上也能看出是同步的处理, 意味该函数会一直阻塞到其设置的block执行完毕.

dispatch_apply

dispatch_apply能够实现把设置的block执行指定的次数.

dispatch_apply(10, queue, ^(size_t index){
    printf("i am %d\n", index);
});

dispatch_suspend/dispatch_resume

dispatch_suspend()函数能够暂停queue, 将其挂起, 那么其中还未开始执行的block就会处于等待的状态, 然后调用dispatch_resume()函数能够再次恢复执行.

dispatch_semaphore

dispatch_semaphore几乎跟操作系统的PV操作一致, 通过信号量保证资源的互斥访问.

NSMutableArray * array = [[NSMutableArray alloc] initWithCapacity:10000];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (int i = 0; i < 10000; ++i) {
    dispatch_async(queue, ^{
        // 一直等待semaphore的value大于等于1, 并且将value减1
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [array addObject:@(i)];
        // 将semaphore的value加1
        dispatch_semaphore_signal(semaphore);
    });
}
dispatch_release(semaphore);

dispatch_once

dispatch_once能够保证其设置的block被执行一次, 多用来实现单例模式.

static id instance;

+ (instancetype)shareInstance {
    static dispatch_once_t once;
    dispatch_once(&once, ^{
        instance = [[XxxClass alloc] init];
    });
    return instance;
}

dispatch I/O

当我们读取大文件时, 其实可以通过concurrent的queue并发读取, 但是已经iOS为我们提供这方面的支持.

下面是一段示例代码:

pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
    close(fd);
});

*out_fd = fdpair[1];

dispatch_io_set_low_water(pipe_channel, SIZE_MAX);

dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
    if (err == 0)
    {
        size_t len = dispatch_data_get_size(pipedata);
        if (len > 0)
        {
            const char *bytes = NULL;
            char *encoded;
            dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
            encoded = asl_core_encode_buffer(bytes, len);
            asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded);
            free(encoded);
            _asl_send_message(NULL, merged_msg, -1, NULL);
            asl_msg_release(merged_msg);
            dispatch_release(md);
        }
    }

    if (done)
    {
        dispatch_semaphore_signal(sem);
        dispatch_release(pipe_channel);
        dispatch_release(pipe_q);
    }
});

GCD的实现

GCD中最多出现的字就是queue, 而queue即是一个先进先出的队列, 队列的实现方式就很多了.

然后并发的执行难免会使用多线程, GCD是在内核级别的多线程的运用, 不同于pthreads/NSThread这种直接对应操作系统线程的方式.

多线程中的资源操作必然会使用到锁, 在上面的函数中我们也看到GCD有使用semaphore信号量.

最后在服务器编程中, 实现高效的运行一般会运用到事件驱动, 在linux中一般为epoll, 而iOS中则为kqueue.

GCD即是通过这些基础的东西实现的, 而且是优雅的, 易用的.

花了两天时间, 把《Objective-C高级编程 iOS与OS X多线程和内存管理》一书全部读完了, 全书分为3章:内存管理,Blocks,GCD. 我也对应的写了3篇总结, 几乎涉及到应用的部分我都有记录下来, 而具体实现的部分实在太过冗长, 也就基本一笔带过了.

读完这本数, 对iOS一些基础的东西有了更清晰的认识, 基本算是入门了.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK