GCD 扩展

调度源

GCD提供一些调度源接口来监测底层系统对象(比如Unix文件描述符、Mach端口、Unix信号、Unix虚拟文件系统节点、自定义源等等)的活动。不像阻塞等待事件发生或者反复轮询查看那样浪费资源也无法度量,调度源像kquque一样自动异步地提交任务到指定调度队列响应处理。

  • dispatch_source_create:创建一个新的调度源来检测底层系统对象,并响应事件自动提交事件回调任务到指定队列执行。调度源开始都是暂停状态,需要使用dispatch_resume启动。不再使用时需要调用dispatch_release释放资源。
  • dispatch_source_set_registration_handler:调度源底层的事件发送策略是异步设置的。在调度源完全设置好并准备开始发送事件时,注册回调任务就会被提交到指定队列执行。在调度源暂停或者正在执行回调任务期间,如果事件到达,事件会合并在一起,等到调度源启动或者回调任务执行完再发送。
  • dispatch_source_set_event_handler:监测的事件发生时,事件回调任务响应并被提交到指定队列执行。其中使用dispatch_source_get_data获取调度源等待处理的数据,其代表意义因调度源类型的不同而不同。
  • dispatch_source_set_cancel_handler:当系统释放了所有对调度源的引用时,取消回调任务响应并被提交到指定队列执行。通常用于清理占用的资源,例如打开的文件或者套接字。
  • dispatch_source_cancel:异步地取消调度源,阻止事件回调任务继续执行。

dispatch_source_t dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)中,type是最重要的参数,因为它决定了handle和mask参数将会是什么。

  • DISPATCH_SOURCE_TYPE_DATA_ADD监测自定义源逻辑Add数据合并,handle和mask未使用。
  • DISPATCH_SOURCE_TYPE_DATA_OR监测自定义源逻辑Or数据合并,handle和mask未使用。
  • DISPATCH_SOURCE_TYPE_MACH_RECV监测Mach端口接收,handle代表Mach端口号,mask未使用。
  • DISPATCH_SOURCE_TYPE_MACH_SEND监测Mach端口发送,handle代表Mach端口号,mask代表发送事件标识
  • DISPATCH_SOURCE_TYPE_PROC监测进程,handle代表进程号,mask代表进程事件标识
  • DISPATCH_SOURCE_TYPE_READ监测文件描述符读,handle代表文件描述符,mask未使用。
  • DISPATCH_SOURCE_TYPE_SIGNAL监测信号,handle代表信号量,mask未使用。
  • DISPATCH_SOURCE_TYPE_TIMER监测定时器,handle和mask未使用。
  • DISPATCH_SOURCE_TYPE_VNODE监测虚拟文件系统节点,handle代表文件描述符,mask代表节点事件标识
  • DISPATCH_SOURCE_TYPE_WRITE监测文件描述符写,handle代表文件描述符,mask未使用。
  • DISPATCH_SOURCE_TYPE_MEMORYPRESSURE监测内存压力,handle未使用,mask代表压力事件标识

自定义源:通过dispatch_source_merge_data主动发送信号产生系统事件。其函数名的来由是,如果当前比较繁忙,GCD会在执行响应方法前,会多次自动联结事件,最终只执行一次响应方法。

dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

__block unsigned long data = 0;
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, mainQueue);
dispatch_source_set_event_handler(source, ^{
    unsigned long delta = dispatch_source_get_data(source);
    data += delta;
});
dispatch_resume(source);

dispatch_apply(100, globalQueue, ^(size_t index) {
    dispatch_source_merge_data(source, 1);
});

文件描述符:使用GCD监测文件描述符读写。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t stdinSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, STDIN_FILENO, 0, globalQueue);
dispatch_source_set_event_handler(stdinSource, ^{
    char buf[1024];
    long len = read(STDIN_FILENO, buf, sizeof(buf));
    if(len > 0)
        NSLog(@"Got data from stdin: %s", buf);
});
dispatch_resume(stdinSource);    

进程:使用GCD监测进程事件,包括进程退出、进程分叉、进程执行和进程信号。

dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSArray *runningApps = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.mail"];
NSRunningApplication *mail = [runningApps count]
? runningApps[0]
: nil;
if (mail) {
    pid_t const pid = mail.processIdentifier;
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, mainQueue);
    dispatch_source_set_event_handler(source, ^(){
        NSLog(@"Mail Quit");
    });
    dispatch_resume(source);
}

文件系统节点:使用GCD监测文件系统节点事件,包括节点删除、节点内容写入、节点大小变化、节点属性变化、节点链接数变化、节点名字变化和节点被删除。

NSURL *directoryURL; // assume this is set to a directory
int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);
if (fd < 0) {
    char buffer[80];
    strerror_r(errno, buffer, sizeof(buffer));
    NSLog(@"Unable to open \"%@\": %s (%d)", [directoryURL path], buffer, errno);
    return;
}
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(source, ^(){
    unsigned long const data = dispatch_source_get_data(source);
    if (data & DISPATCH_VNODE_WRITE) {
        NSLog(@"The directory changed.");
    }
    if (data & DISPATCH_VNODE_DELETE) {
        NSLog(@"The directory has been deleted.");
    }
});
dispatch_source_set_cancel_handler(source, ^(){
    close(fd);
});
dispatch_resume(source);

信号:使用GCD监测信号,这些信号可在signal.h中找到。比如,信号SIGSTOP将会在进程接收到一个无法回避的暂停指令时被发出。LLDB调试器调试应用时使用的也是这个信号。

dispatch_queue_t mainQueue = dispatch_get_main_queue();
__typeof(self) __weak weakSelf = self;

static dispatch_source_t source = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGSTOP, 0, mainQueue);
    if (source) {
        dispatch_source_set_event_handler(source, ^{
            NSLog(@"Hi, I am: %@", weakSelf);
        });
        dispatch_resume(source);
    }
});

定时器:使用GCD监测定时器。GCD不保证计时器100%精准触发,由参数leeway决定了触发精准度。其意义是为了降低资源消耗,因为系统可以让CPU休息足够长的时间,尽量每次醒来时执行一个任务集合,而不是不断醒来只执行一个任务,这样效率更高。比如较大的leeway允许系统拖延计时器触发,尽量拖延到与其他任务一起执行。

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_timer(source, DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(source, ^(){
    NSLog(@"Time flies.");
});
dispatch_resume(source);

调度I/O管道

调度I/O通道提供了一种与读写文件描述符不同的方式。有流和随机存取两种类型的管道。比如,硬盘文件可以创建一个随机存取管道(因为文件描述符是可寻址的),套接字可以创建一个流管道。和文件描述符一样,I/O管道也拥有读写操作,但这些操作都是以非阻塞,异步I/O的形式高效实现的。I/O管道也有同步读写操作的屏障操作,还可以设置最高和最低字节数限制,操作周期。

调度缓冲区

以往处理多个小而散的缓冲区数据时,通常会将它们组合成大缓冲区再进行处理,但这样不仅会复制大量数据,而且大量I/O操作会在降低性能占用更多资源。很多旧C的API有个问题是,缓冲区没有所有权的概念,所以不得不将数据再次拷贝以保证使用期间有效。GCD的dispatch_data_t,在某种程度上和Objc的NSData很相似,但是必须记住GCD只是纯C的API不能使用Objc对象。dispatch_data_t(基于栈或基于栈)使得对应的缓冲区有了所有权的概念能确保有效性不产生拷贝操作,而且其相当独特的是可以基于多个零碎的内存区域抽象成一个连续的内存区域来提供使用。

  • dispatch_data_create:创建调度缓冲区对象,指定析构回调任务和执行队列。
  • dispatch_data_create_map:返回一个拷贝原缓冲区对象内容且内存连续分布的新缓冲区对象。
  • dispatch_data_create_concat:返回一完整引用了两个缓冲区对象的新缓冲区对象。
  • dispatch_data_create_subrange:返回一个引用原缓冲区对象部分的新缓冲区对象。
  • dispatch_data_apply:贯穿整个缓冲区对象每一个子区域并执行方法。
  • dispatch_data_copy_region:返回一个拷贝原缓冲区对象部分内容的新缓冲区对象。

基准测试

GCD有一个适合优化代码的灵巧小工具:uint64_t dispatch_benchmark(size_t count, void (^block)(void));文件头部声明该函数类型,就能够测量测试代码执行的平均纳秒数:

size_t const objectCount = 100;
uint64_t n = dispatch_benchmark(1000, ^{
    @autoreleasepool {
        id obj = @42;
        NSMutableArray *array = [NSMutableArray array];
        for (size_t i = 0; i < objectCount; ++i) {
            [array addObject:obj];
        }
    }
});
NSLog(@"-[NSMutableArray addObject:] : %llu ns", n);

延伸阅读


GCD 扩展
https://hllovesgithub.github.io/2015/10/25/2015-10-25-GCD 扩展/
作者
Hu Liang
发布于
2015年10月25日
许可协议