iOS底层 - Dispatch Source

1,786 阅读7分钟

这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战

写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。

目录如下:

  1. iOS 底层原理探索 之 alloc
  2. iOS 底层原理探索 之 结构体内存对齐
  3. iOS 底层原理探索 之 对象的本质 & isa的底层实现
  4. iOS 底层原理探索 之 isa - 类的底层原理结构(上)
  5. iOS 底层原理探索 之 isa - 类的底层原理结构(中)
  6. iOS 底层原理探索 之 isa - 类的底层原理结构(下)
  7. iOS 底层原理探索 之 Runtime运行时&方法的本质
  8. iOS 底层原理探索 之 objc_msgSend
  9. iOS 底层原理探索 之 Runtime运行时慢速查找流程
  10. iOS 底层原理探索 之 动态方法决议
  11. iOS 底层原理探索 之 消息转发流程
  12. iOS 底层原理探索 之 应用程序加载原理dyld (上)
  13. iOS 底层原理探索 之 应用程序加载原理dyld (下)
  14. iOS 底层原理探索 之 类的加载
  15. iOS 底层原理探索 之 分类的加载
  16. iOS 底层原理探索 之 关联对象
  17. iOS底层原理探索 之 魔法师KVC
  18. iOS底层原理探索 之 KVO原理|8月更文挑战
  19. iOS底层原理探索 之 重写KVO|8月更文挑战
  20. iOS底层原理探索 之 多线程原理|8月更文挑战
  21. iOS底层原理探索 之 GCD函数和队列
  22. iOS底层原理探索 之 GCD原理(上)
  23. iOS底层 - 关于死锁,你了解多少?
  24. iOS底层 - 单例 销毁 可否 ?

以上内容的总结专栏


细枝末节整理


前言

最近关于GCD的探索也要告一段落了,今天和大家一起学习下 Dispatch Source。和之前不一样的是,今天研究下这个 Dispatch Source 并且,用它来实现一个 比 NSTimer 更准确的 自定义Timer。 好了,这就开始今天的内容吧!

Dispatch Source

Dispatch Source 是 BSD 系统内核惯有功能kqueue的包装,kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。它的CPU负荷非常小,尽量不占用资源。kqueue可以说是应用程序处理XUN内核中发生的各种事件的方法中最优秀的一种。

当事件发生时,Dispatch Source 会在制定的 Dispatch Queue 中执行事件的处理。

Dispatch Source 的类型

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
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,用来定义我们在一秒计时到的时候,执行的任务。

typedef void(^task)(void);

定义两个方法:

/// 添加要执行的任务 每秒回调一次
- (void)executeTask:(task)task;

/// 开启 或 暂停 计时
- (void)starOrPause;

自定义一个dispatch_source_t

    //自定义串行队列
    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);

实现 开启或暂停 方法 :

if (!self.isRefreshing) {
    // 不在进行计 - 就 开始计时
        
    dispatch_resume(self.timer);
    self.isRefreshing = YES;
    NSLog(@"开始");
}else {
    //正在计时 - 就 挂起
        
    dispatch_suspend(self.timer);
    self.isRefreshing = NO;
    NSLog(@"挂起");
}

实现添加任务 :

dispatch_source_set_event_handler(self.timer, task);

这样,计时器就写好了。