从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.简单的理...

iOS中的图像处理(一)——基础滤镜

最近在稍微做一些整理,翻起这部分的代码,发现是两个多月前的了。 这里讨论的是基于RGBA模型下的图像处理,即将变换作用在每个像素上。 代码是以UIImage的category形式存在的: t...

iOS中线程Call Stack的捕获和解析(一)

这里对上个月做的一个技术项目做部分技术小结,这篇文章描述的功能和我们在使用Xcode进行调试时点击暂停的效果类似。一、获取任意一个线程的Call Stack如果要获取当前线程的调用栈,可以直接使用现有...

iOS中线程Call Stack的捕获和解析(二)

上接iOS中线程Call Stack的捕获和解析(一)。1. 部分参考资料做这一块时也是查阅了很多链接和书籍,包括但不限于: 《OS X ABI Mach-O File Format Referenc...

iOS中的图像处理(三)——混合运算

有时候,单独对一张图像进行处理是很难或者根本达不到我们想要的效果的。一个好的滤镜效果的诞生,往往要经过很多复杂步骤、细致微调、图片应用效果观察以及很多图层叠加。 我在JSWidget上发现了一些常用...

iOS中的图像处理(二)——卷积运算

关于图像处理中的卷积运算,这里有两份简明扼要的介绍:文一,文二。 其中,可能的一种卷积运算代码如下: - (UIImage*)applyConvolution:(NSArray*)kernel...

来淘宝的这一年:前篇、生活和工作

Jason Lee http://blog.csdn.net/jasonblog http://weibo.com/jasonmblog 我犹豫着要用“这一年”还是“这半年”的字眼。距离...

iOS应用的crash日志的分析基础

Outline 如何获得crash日志 如何解析crash日志 如何分析crash日志      1. iOS策略相关      2. 常见错误标识      3. 代码bug ...

在使用CocoaPod的Xcode工程中断点没有调试信息的问题跟进

通常客户端产品至少都会有Debug和Release两种编译配置,在编译Release版本中会进行一些优化,以减少最终产品的体积。 比如,在Release版本中会对代码执行步骤进行优化(如O1、O2等...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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