从NSTimer的失效性谈起(一):关于NSTimer和NSRunLoop

原创 2016年03月15日 20:33:07

一、NSTimer的失效性

在iOS中要设置一个定时器的通常做法是调用如下API:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

这个API会创建一个NSTimer对象,将其添加到当前runloop的defaultMode中,然后返回该对象,如下所说:

Creates and returns a new NSTimer object and schedules it on the current run loop in the default mode.

由于NSTimer的过期事件需要由NSRunLoop来执行:

而在次线程上我们还需要额外显式地开启次线程所对应的runloop(比较麻烦),所以通常我们都会在主线程直接调用该API来设置并启动一个定时器。

一个NSRunLoop有几种mode,目前我们接触比较多的是NSDefaultRunLoopModeUITrackingRunLoopMode,而NSRunLoopCommonModes更多类似于一个标志,可以通过如下API将某一种mode归进commonModes:

CF_EXPORT void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode);

这样一来,我们就可以通过将定时器添加到NSRunLoopCommonModes来一次性添加到多个对应的mode中。

通常,我们添加到defaultMode的定时器是可以正常工作的,不过当用户对scrollView进行滑动时定时器就失效了。这是因为此时mainRunLoop从NSDefaultRunLoopMode退出,而进入到了UITrackingRunLoopMode,所以添加到前者的定时器不会得到处理。

二、验证mainRunLoop的mode变化

关于NSRunLoop的进一步探究,可以参考:Run LoopsNSRunLoop InternalsCFRunLoop.c深入理解RunLoop

我们可以通过RunLoopObserver来观察一个runloop的mode切换:

    CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        if (activity == kCFRunLoopExit) {
            NSLog(@"runloop mode %@ exit\n", [[NSRunLoop currentRunLoop] currentMode]);
        } else if (activity == kCFRunLoopEntry) {
            NSLog(@"runloop mode %@ entry\n", [[NSRunLoop currentRunLoop] currentMode]);
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetMain(), observerRef, kCFRunLoopCommonModes);

之前我猜想:对于mainRunLoop来说,一旦进入就不会退出了,除非应用程序结束。经过模拟,可以发现在滑动scrollView的时候会触发kCFRunLoopExitkCFRunLoopEntry事件,断点得到的调用栈如下图:

entry

通过上图调用栈来对应CFRunLoop.c源码可以找到相应方法调用:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    ... // 省略部分代码

    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    ... // 省略部分代码
    return result;
}

可以看到在CFRunLoopRunSpecific这一层对应的是kCFRunLoopEntrykCFRunLoopExit的回调,而具体事件处理则落在了__CFRunLoopRun内:

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();
    ... // 省略部分代码

    dispatch_source_t timeout_timer = NULL;
    ... // 省略部分代码

    int32_t retVal = 0;
    do {
        ... // 省略部分代码
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        __CFRunLoopDoBlocks(rl, rlm);
        ... // 省略部分代码

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        ... // 省略部分代码

        if (!poll) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        ... // 省略部分代码

        if (!poll) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        ... // 省略部分代码
    } while (0 == retVal);
    return retVal;
}

可以看到在__CFRunLoopRun中负责剩余几种状态的回调:kCFRunLoopBeforeTimerskCFRunLoopBeforeSourceskCFRunLoopBeforeWaitingkCFRunLoopAfterWaiting,对应着如下调用栈:

beforeTimer

综上可以验证在开始滚动和停止滚动的时候,mainRunLoop确实会进行mode的切换。开始滚动时从NSDefaultRunLoopMode切换到UITrackingRunLoopMode(可能是为了让滚动更顺畅?),停止滚动时则反过来。

而不同mode维护着各自的inputSources和timerSources,所以在UITrackingRunLoopMode下不会处理添加到NSDefaultRunLoopMode的定时器。

iOS开发学习之NSTimer失效、NSTimer与runloop之间的关系、解密NSTimer

1. 今天在开发的时候,遇到NSTimer无效、所以也到网上找了一些资料,看看究竟怎么回事儿、     再次也做一次分享、方便有需要的朋友。  1.   NSTimer是做什么的? 1.简单的理...
  • zhonggaorong
  • zhonggaorong
  • 2016年06月07日 11:10
  • 1527

NSTimer方法不执行的问题

最近, 在使用NSTimer的时候,发现了一个问题,在当前界面NSTimer的方法是可以执行的,但是当我push到下一界面, 做完相关操作,pop回来的时候,UI刷新,再次调用定时器时,发现方法竟然不...
  • ismilesky
  • ismilesky
  • 2016年12月08日 14:04
  • 2701

从NSTimer的失效性谈起(一):关于NSTimer和NSRunLoop

一、NSTimer的失效性在iOS中要设置一个定时器的通常做法是调用如下API:+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)...
  • jasonblog
  • jasonblog
  • 2016年03月15日 20:33
  • 2919

NSTimer与NSRunLoop

NSTimer使用方法参考How do I use NSTimer?,使用NSTimer的方式如下: scheduled timer & 使用 selector,这是最常用的方式NSTimer *t ...
  • u014084081
  • u014084081
  • 2015年09月08日 09:16
  • 601

NSRunLoop和NSTimer

在使用NSTimer的时候遇到过到了设定的时间NSTimer指定的方法不执行的情况,发现调用NSTimer不是在主线程,需要将NSTimer添加到NSRunloop中。下面特酷吧根据自己实际开发总结使...
  • baidu_31071595
  • baidu_31071595
  • 2016年03月08日 22:29
  • 275

将NSTimer添加至RunLoop中的两种方法区别

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)l...
  • intheair100
  • intheair100
  • 2015年07月18日 14:32
  • 4622

NSTimer与NSRunLoop的关系分析

发表于 2013 年 6 月 27 日 由 bluev | 416 次浏览 最近关于NSTimer和NSRunLoop的关系,做了一个小试验。代码地址:https://github.com/...
  • meegomeego
  • meegomeego
  • 2013年10月31日 17:58
  • 732

runloop,线程影响nstimer掉用的原因和解决

根据之前的经验总结, runloop,线程,滑动scrollerview 都会影响nastimer的掉用。 讲述着四者之间的影响,说先从runloop说起,runloop是消息机制,iO...
  • u013896628
  • u013896628
  • 2015年12月03日 09:57
  • 773

NSTimer和NSRunLoop在子线程中的使用

一 : 什么是NSTimer? 官方给出解释是“A timer provides a way to perform a delayed action or a periodic action. Th...
  • developerhk
  • developerhk
  • 2014年04月21日 11:17
  • 2821

swift代码之路(五)NSTimer

public class NSTimer : NSObject 定时器的作用: 1、在指定的时间执行指定的任务 2、间隔一段时间执行指定任务 定时器的创建 定时器有两种创建方式 (1)sche...
  • shan1991fei
  • shan1991fei
  • 2016年09月21日 13:46
  • 479
收藏助手
不良信息举报
您举报文章:从NSTimer的失效性谈起(一):关于NSTimer和NSRunLoop
举报原因:
原因补充:

(最多只允许输入30个字)