这是我参与8月更文挑战的第12天,活动详情查看:8月更文挑战
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
复制代码
目录如下:
- iOS 底层原理探索 之 alloc
- iOS 底层原理探索 之 结构体内存对齐
- iOS 底层原理探索 之 对象的本质 & isa的底层实现
- iOS 底层原理探索 之 isa – 类的底层原理结构(上)
- iOS 底层原理探索 之 isa – 类的底层原理结构(中)
- iOS 底层原理探索 之 isa – 类的底层原理结构(下)
- iOS 底层原理探索 之 Runtime运行时&方法的本质
- iOS 底层原理探索 之 objc_msgSend
- iOS 底层原理探索 之 Runtime运行时慢速查找流程
- iOS 底层原理探索 之 动态方法决议
- iOS 底层原理探索 之 消息转发流程
- iOS 底层原理探索 之 应用程序加载原理dyld (上)
- iOS 底层原理探索 之 应用程序加载原理dyld (下)
- iOS 底层原理探索 之 类的加载
- iOS 底层原理探索 之 分类的加载
- iOS 底层原理探索 之 关联对象
- iOS底层原理探索 之 魔法师KVC
- iOS底层原理探索 之 KVO原理|8月更文挑战
- iOS底层原理探索 之 重写KVO|8月更文挑战
- iOS底层原理探索 之 多线程原理|8月更文挑战
- iOS底层原理探索 之 GCD函数和队列
- iOS底层原理探索 之 GCD原理(上)
- iOS底层 – 关于死锁,你了解多少?
- 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);
复制代码
这样,计时器就写好了。