【iOS底层技术】- Dispatch Source

2022-01-11 16:04:12 浏览数 (1)

前言

最近关于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。
代码语言:javascript复制
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的结果是未定义的。
  • 要提交到源目标队列的事件处理程序块。
代码语言:javascript复制
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与挂起数据合并的值 由分派源类型指定。值为零没有影响 并且不会导致事件处理程序块的提交。
代码语言:javascript复制
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的结果是未定义的。返回值应该根据分派的类型来解释 来源,并可能是下列之一:
代码语言:javascript复制
 *  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的结果是未定义的。
代码语言:javascript复制
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的结果是未定义的。
代码语言:javascript复制
API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
void
dispatch_suspend(dispatch_object_t object);

滑动显示更多

取消

  • 异步地取消分派源,防止任何进一步调用 事件处理程序块的。
  • 取消将阻止对事件处理程序块的任何进一步调用 指定的分派源,但不中断事件处理程序 正在进行中的区块的时候,取消处理程序被提交到源的目标队列 源的事件处理程序已经完成,表明现在可以安全关闭了 源的句柄(例如文件描述符或Mach端口)。
  • 要取消的调度源。在这个参数中传递NULL的结果是未定义的。
代码语言:javascript复制
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);

这样,计时器就写好了。

0 人点赞