美文网首页程序员
dispatch_after与performSelectorwi

dispatch_after与performSelectorwi

作者: 面试小集 | 来源:发表于2018-04-04 10:26 被阅读95次

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

    • 使用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:

    相关文章

      网友评论

        本文标题:dispatch_after与performSelectorwi

        本文链接:https://www.haomeiwen.com/subject/trmchftx.html