×

dispatch_after与performSelectorwithObjectafterDelay 比较

96
RiverLi
2018.04.04 10:26 字数 713

在日常开发中,我们会经常遇到一些延迟执行的需求,通常的实现方式有:

  • 使用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来实现。

参考

深入理解GCD
performSelector:withObject:afterDelay:

iOS开发
Web note ad 1