dispatch_after与performSelectorwi
在日常开发中,我们会经常遇到一些延迟执行的需求,通常的实现方式有:
- 使用dispatch_after
- 使用performSelector:withObject:afterDelay:
本文主要分析这两种方法使用时候的注意事项以及实现原理
dispatch_after
函数声明:
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
参数解析:
when: 时间节点,使用dispatch_time
或者dispatch_walltime
创建。
queue: block所提交的队列,这个队列由系统强引用,直到block执行完毕。不能为NULL。
block: 需要提交的任务,该函数会自动执行Block_copy
and Block_release
方法。不能为NULL。
讨论:
该方法等到指定的时间节点后异步地将block任务添加到指定的队列。可以将DISPATCH_TIME_NOW
传递给when参数,但这种做法不如直接调用dispatch_async
。将DISPATCH_TIME_FOREVER
传递给when参数是没有任何意义的。
实现原理:
dispatch_after的实现是依赖于定时器dispatch_source_set_timer
。
void dispatch_after_f(dispatch_time_t when,
dispatch_queue_t queue,
void *ctxt,
dispatch_function_t func) {
uint64_t delta;
struct _dispatch_after_time_s *datc = NULL;
dispatch_source_t ds;
// 如果延迟为 0,直接调用 dispatch_async
delta = _dispatch_timeout(when);
if (delta == 0) {
return dispatch_async_f(queue, ctxt, func);
}
ds = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_assert(ds);
datc = malloc(sizeof(*datc));
dispatch_assert(datc);
datc->datc_ctxt = ctxt;
datc->datc_func = func;
datc->ds = ds;
dispatch_set_context(ds, datc);
dispatch_source_set_event_handler_f(ds, _dispatch_after_timer_callback);
dispatch_source_set_timer(ds, when, DISPATCH_TIME_FOREVER, 0);
dispatch_resume(ds);
}
首先将延迟执行的 block 封装在 _dispatch_after_time_s
这个结构体中,并且作为上下文,与 timer 绑定,然后启动 timer。
到时以后,执行 _dispatch_after_timer_callback
回调,并取出上下文中的 block:
static void _dispatch_after_timer_callback(void *ctxt) {
struct _dispatch_after_time_s *datc = ctxt;
_dispatch_client_callout(datc->datc_ctxt, datc->datc_func);
// 清理工作
}
performSelector:withObject:afterDelay:
函数声明:
- (void)performSelector:(SEL)aSelector
withObject:(id)anArgument
afterDelay:(NSTimeInterval)delay;
参数解析:
aSelector: 使用Selector
将被调用的方法。该方法没有返回值,参数是id类型或者没有参数。
anArgument: 方法被调用的时候传递给方法的参数,如果方法没有参数,传nil。
delay: 方法被调用之前等待的最小时间。指定为0,方法可能不会被立即执行。方法会在当前线程的runloop循环中排队并尽可能快的被执行。
讨论:
该方法在当前线程的runloop上设置一个定时器来执行aSelector
。定时器默认在NSDefaultRunLoopMode
模式下执行。当时间到时,当前线程尝试从runloop队列中取出这个方法并执行。如果runloop处于NSDefaultRunLoopMode
模式下,方法顺利被执行,否则定时器一直等到runloop处于NSDefaultRunLoopMode
下才执行。
我们可以使用performSelector:withObject:afterDelay:inModes:
方法来指定定时器在哪些mode下执行。
使用performSelectorOnMainThread:withObject:waitUntilDone:
和performSelectorOnMainThread:withObject:waitUntilDone:modes:
方法来确保在主线程中执行。
使用cancelPreviousPerformRequestsWithTarget:
和cancelPreviousPerformRequestsWithTarget:selector:object:
方法来取消特定的任务。
注意:该方法的执行依赖于runloop,子线程默认情况下并不开启runloop,如果你需要在这种情况下使用延迟功能,应当考虑是否开启runloop,还是使用dispatch_after来实现。