先看看Runloop最常见的应用,滑动页面时定时器会停止,所以要处理:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"1");
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode: NSRunLoopCommonModes];
原因:
RunLoopMode
可以切换,默认为kCFRunLoopDefaultMode
,滑动时为UITrackingRunLoopMode
,启动时为UIInitializationRunLoopMode
。timer
加入的RunLoopMode
默认是kCFRunLoopDefaultMode
,当页面滑动时,RunLoopMode
自动切换到UITrackingRunLoopMode
,因此timer
失效。当停止滑动时,RunLoopMode
又切换回kCFRunLoopDefaultMode
,timer
恢复。
- 我们从源码看看做了什么:
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
...
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;//<CFSetRef>{defaultMode, TrackingMode}
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);//懒加载
}
CFSetAddValue(rl->_commonModeItems, rlt);//添加timer
if (NULL != set) {
CFTypeRef context[2] = {rl, rlt};
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);//遍历_commonModes,将 timer 添加到 commonModes 的所有模式下
CFRelease(set);
}
} else {
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);//找到相关mode
...
}
__CFRunLoopUnlock(rl);
}
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
CFStringRef modeName = (CFStringRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) { ... } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
...
} else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);//再调用
}
}
解决:
timer
加入到kCFRunLoopCommonModes
后,会将timer
加入_commonModeItems
中,并将timer
加入commonModes
的所有mode
中,所以不管页面滑动还是静止,(在所有Runloop
模式中)timer
都能生效。
网友评论