前言
最近关于GCD的探索也要告一段落了,今天和大家一起学习下 Dispatch Source。
和之前不一样的是,今天研究下这个 Dispatch Source 并且,用它来实现一个 比 NSTimer 更准确的 自定义Timer。好了,这就开始今天的内容吧!
Dispatch Source
Dispatch Source 是 BSD 系统内核惯有功能kqueue的包装,kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。
它的CPU负荷非常小,尽量不占用资源。kqueue可以说是应用程序处理XUN内核中发生的各种事件的方法中最优秀的一种。
当事件发生时,Dispatch Source 会在制定的 Dispatch Queue 中执行事件的处理。
Dispatch Source 的类型
代码语言:javascript复制typedef const struct dispatch_source_type_s *dispatch_source_type_t;
#define DISPATCH_SOURCE_TYPE_DATA_ADD 自定义的事件,变量增加
#define DISPATCH_SOURCE_TYPE_DATA_OR 自定义的事件,变量OR
#define DISPATCH_SOURCE_TYPE_DATA_REPLACE 自定义的事件,变量Replace
#define DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送
#define DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
#define DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存报警
#define DISPATCH_SOURCE_TYPE_PROC 进程监听,如进程的退出、创建一个或更多的子线程、进程收到UNIX信号
#define DISPATCH_SOURCE_TYPE_READ IO操作,如对文件的操作、socket操作的读响应
#define DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信号时响应
#define DISPATCH_SOURCE_TYPE_TIMER 定时器
#define DISPATCH_SOURCE_TYPE_VNODE 文件状态监听,文件被删除、移动、重命名
#define DISPATCH_SOURCE_TYPE_WRITE IO操作,如对文件的操作、socket操作的写响应
滑动显示更多
Dispatch Source 的使用
创建 Dispatch Source
- 创建一个新的分派源来监视低级系统对象和自动 ,以malatic方式向调度队列提交处理程序块以响应事件。
- 分派源不可重入。分派时收到的任何事件 源被挂起或事件处理程序块当前正在执行时 是在调派源恢复后还是在 事件处理程序块已返回。
- 调度源是在非活动状态下创建的。在创建了 来源和设置任何想要的属性(例如,处理程序,上下文等),为了开始事件传递,必须调用dispatch_activate()。一旦源被激活,就调用dispatch_set_target_queue() 是不允许的(参见dispatch_activate()和dispatch_set_target_queue())。
- 出于向后兼容性的原因,dispatch_resume() 否则,挂起的源和调用有同样的效果 dispatch_activate()。对于新代码,最好使用dispatch_activate()。声明分派源的类型。一定是其中一个定义 dispatch_source_type_t常数。
- 要监视的底层系统句柄。这个论点的解释 由类型参数中提供的常量决定。
- 指定所需事件的标志掩码。的解释 此实参由类型形参中提供的常量决定。
- 事件处理程序块将提交到的调度队列。
- 如果queue是DISPATCH_TARGET_QUEUE_DEFAULT,源将提交事件 默认优先级全局队列的处理程序块。
- 新创建的分派源。如果传入的参数无效,则为NULL。
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_source_t
dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
uintptr_t mask,
dispatch_queue_t _Nullable queue);
滑动显示更多
设置事件处理器
- 为给定的分派源设置事件处理程序块。
- 要修改的调度源。在这个参数中传递NULL的结果是未定义的。
- 要提交到源目标队列的事件处理程序块。
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL1 DISPATCH_NOTHROW
void
dispatch_source_set_event_handler(dispatch_source_t source,
dispatch_block_t _Nullable handler);
滑动显示更多
源事件设置数据
- 将数据合并到类型为DISPATCH_SOURCE_TYPE_DATA_ADD的分派源中,
- DISPATCH_SOURCE_TYPE_DATA_OR
- 或DISPATCH_SOURCE_TYPE_DATA_REPLACE,
- 并将其事件处理程序块提交给目标队列。
- 在这个参数中传递NULL的结果是未定义的
- 要使用逻辑OR或ADD与挂起数据合并的值 由分派源类型指定。值为零没有影响 并且不会导致事件处理程序块的提交。
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_source_merge_data(dispatch_source_t source, uintptr_t value);
滑动显示更多
获取源事件数据
- 返回分派源的挂起数据
- 此函数打算从事件处理程序块中调用。在事件处理程序回调之外调用此函数的结果是 未定义的。在这个参数中传递NULL的结果是未定义的。返回值应该根据分派的类型来解释 来源,并可能是下列之一:
* DISPATCH_SOURCE_TYPE_DATA_ADD: application defined data
* DISPATCH_SOURCE_TYPE_DATA_OR: application defined data
* DISPATCH_SOURCE_TYPE_DATA_REPLACE: application defined data
* DISPATCH_SOURCE_TYPE_MACH_SEND: dispatch_source_mach_send_flags_t
* DISPATCH_SOURCE_TYPE_MACH_RECV: dispatch_source_mach_recv_flags_t
* DISPATCH_SOURCE_TYPE_MEMORYPRESSURE dispatch_source_memorypressure_flags_t
* DISPATCH_SOURCE_TYPE_PROC: dispatch_source_proc_flags_t
* DISPATCH_SOURCE_TYPE_READ: estimated bytes available to read
* DISPATCH_SOURCE_TYPE_SIGNAL: number of signals delivered since
* the last handler invocation
* DISPATCH_SOURCE_TYPE_TIMER: number of times the timer has fired
* since the last handler invocation
* DISPATCH_SOURCE_TYPE_VNODE: dispatch_source_vnode_flags_t
* DISPATCH_SOURCE_TYPE_WRITE: estimated buffer space available
滑动显示更多
代码语言:javascript复制API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_WARN_RESULT DISPATCH_PURE
DISPATCH_NOTHROW
uintptr_t
dispatch_source_get_data(dispatch_source_t source);
滑动显示更多
继续
- 恢复对分派对象的块调用。
- 分派对象可以用dispatch_suspend()挂起,它会递增 内部暂停计数。Dispatch_resume()是相反的操作, 并消耗暂停计数。当最后一次挂起计数被消耗时, 与该对象关联的块将再次被调用。
- 出于向后兼容性的原因,dispatch_resume()在非激活和非激活状态下 否则,挂起的分派源对象具有与调用相同的效果 dispatch_activate()。对于新代码,最好使用dispatch_activate()。
- 如果指定的对象挂起计数为零且不是非活动的 源,此函数将导致断言和流程 终止。
- 要恢复的对象。在这个参数中传递NULL的结果是未定义的。
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_resume(dispatch_object_t object);
滑动显示更多
挂起
- 暂停对分派对象上的块的调用。
- 挂起的对象将不会调用与它关联的任何块。对象的挂起将发生在关联的任何运行块之后 对象完成。
- dispatch_suspend()的调用必须与调用平衡dispatch_resume()。
- 被悬挂的物体。在这个参数中传递NULL的结果是未定义的。
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_suspend(dispatch_object_t object);
滑动显示更多
取消
- 异步地取消分派源,防止任何进一步调用 事件处理程序块的。
- 取消将阻止对事件处理程序块的任何进一步调用 指定的分派源,但不中断事件处理程序 正在进行中的区块的时候,取消处理程序被提交到源的目标队列 源的事件处理程序已经完成,表明现在可以安全关闭了 源的句柄(例如文件描述符或Mach端口)。
- 要取消的调度源。在这个参数中传递NULL的结果是未定义的。
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_source_cancel(dispatch_source_t source);
滑动显示更多
自定义Timer
Dispatch Source 的使用大致就是上面的流程,下面我们自己实现一个计时器(每秒触发一次)。
定义一个block,用来定义我们在一秒计时到的时候,执行的任务。
代码语言:javascript复制typedef void(^task)(void);
定义两个方法:
代码语言:javascript复制/// 添加要执行的任务 每秒回调一次
- (void)executeTask:(task)task;
/// 开启 或 暂停 计时
- (void)starOrPause;
自定义一个dispatch_source_t:
代码语言:javascript复制 //自定义串行队列
dispatch_queue_t queue = dispatch_queue_create("com.monkey.timer", DISPATCH_QUEUE_SERIAL);
//定时器模式
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//每秒触发
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), NSEC_PER_SEC * 1, 0);
滑动显示更多
实现开启或暂停方法 :
代码语言:javascript复制if (!self.isRefreshing) {
// 不在进行计 - 就 开始计时
dispatch_resume(self.timer);
self.isRefreshing = YES;
NSLog(@"开始");
}else {
//正在计时 - 就 挂起
dispatch_suspend(self.timer);
self.isRefreshing = NO;
NSLog(@"挂起");
}
滑动显示更多
实现添加任务 :
代码语言:javascript复制dispatch_source_set_event_handler(self.timer, task);
这样,计时器就写好了。