* author:conowen@大钟
* E-mail:[email protected]
1、RunLoop定义
从字面上看,run是运行,执行的意思,loop是循环的意思,其实RunLoop就是运行循环的意思。一般来说,一个线程一次只能执行一个任务,执行完了,线程就跑完退出。那么下一个新的任务来,我们还需要重新创建和配置一个线程,这样的话,就比较消耗性能。而RunLoop就可以让线程一直在循环跑,而不退出。RunLoop的内部其实就是一个do while循环(除非你手动传入退出RunLoop的事件),但是它并不是像普通的while循环一样,让CPU一直在工作,一直在检测新的事件。 当有新的事件进来的时候,RunLoop就通知线程去工作,当没有新的事件的时候,RunLoop就会进入休眠状态,节约系统资源。
例如主线程,应用启动的时候,默认就启动了主线程的RunLoop,一直在监听用户的屏幕触摸时间,键盘输入事件等,当应用被杀掉的时候,主线程结束,RunLoop就被销毁了。
附上CFRunLoop源代码CFRunLoop.c
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {//就是一个do while循环
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
2、Runloop与线程
首先,每个线程都有一个绑定的RunLoop对象,包括主线程。RunLoop不需要用户创建,你可以通过函数来获取当前的Runloop,当你第一次获取当前线程RunLoop的时候,就会被创建,而RunLoop是随着线程的结束就销毁。
iOS中有两套API来访问和使用RunLoop
- Foundation框架:NSRunLoop
- Core Foundation框架:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象,NSRunLoop是基于CFRunLoopRef的一层OC封装而已。
//获得RunLoop对象
//Foundation NSRunLoop
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
//Core Foundation NSRunLoop
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
非主线程的RunLoop不会自动运行,你必须在传入驱动事件才能启动RunLoop。
Runloop事件源与Observer
RunLoop有两种事件源:
input sources
1>Port-Based Sources,系统底层的 Port 事件,例如 CFSocketRef ,在应用层基本用不到
2>Custom Input Sources,用户手动创建的 Source
3>Cocoa Perform Selector Sources, Cocoa 提供的 performSelector 系列方法,也是一种事件源Timer sources
1>Timer Source 定时器事件
RunLoop在处理上述了这两种sources时,会产生通知,通知内容包含RunLoop的状态信息,我们可以在线程中注册RunLoop Observer来监听Runloop的行为与状态变化,然后根据这些通知,做出相对应的操作。
具体来说,注册一个RunLoop Observer可以监听RunLoop以下几种状态变态
- 当RunLoop的准备运行的时候
- 当RunLoop准备处理一个Timer sources的时候
- 当RunLoop准备处理一个input sources的时候
- 当RunLoop准备休眠的时候
- 当RunLoop被唤醒时,准备执行这个事件的时候
- 当RunLoop准备退出的时候
对应着下面的源代码
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
有个技巧,通过回调runloop的生命周期函数,可以知道当前loop是否卡顿,做一些卡顿判断。如果每次runloop的loop开始到结束如果超过1/60s,那就应该是出现了卡帧现象了。
3、RunLoop的Mode
RunLoop的Mode其实就是input sources 、Timer sources和RunLoop Observer三者的集合。一个Runloop包含若干个Mode,一个Mode包含着若干个source和Observer,当你RunLoop在运行的时候,你必须(而且只能)给它指定一个特定的Mode(CurrentMode)。当要变换Mode的时候,只能退出当前loop(退出当前event循环),重新指定的Mode再运行。
简单来说,runloop本质是消息循环处理的一套机制,它的每次loop的其实处理一次Mode的过程,Mode包含input sources 、Timer sources和RunLoop Observer三者的集合,每次loop只能处理一次Mode。
以下是五种Mode类型。
Default | NSDefaultRunLoopMode kCFRunLoopDefaultMode(Core Foundation) | The default mode is the one used for most operations. Most of the time, you should use this mode to start your run loop and configure your input sources. Default模式,一般情况下应使用此Mode。主线程RunLoop启动的就是默认模式 |
---|---|---|
Connection | NSConnectionReplyMode(Cocoa) | Cocoa uses this mode in conjunction with NSConnection objects to monitor replies. You should rarely need to use this mode yourself. 使用这个Mode来监控NSConnection对象的回复,用户基本不会使用此Mode。 |
Modal | NSModalPanelRunLoopMode(Cocoa) | Cocoa uses this mode to identify events intended for modal panels. 使用这个Mode来处理modal panels的事件 |
Event tracking | NSEventTrackingRunLoopMode(Cocoa) | Cocoa uses this mode to restrict incoming events during mouse-dragging loops and other sorts of user interface tracking loops. 处理用户的一些拖动事件,如UIScrollView的滚动 |
Common modes | NSRunLoopCommonModes(Cocoa) kCFRunLoopCommonModes(Core Foundation) | This is a configurable group of commonly used modes. Associating an input source with this mode also associates it with each of the modes in the group. For Cocoa applications, this set includes the default, modal, and event tracking modes by default. Core Foundation includes just the default mode initially. You can add custom modes to the set using theCFRunLoopAddCommonMode funtion.事实上这并不是一个Mode,这只是有关联关系的“Mode”,像一个“标识”,如果设置RunLoop的Mode为这个模式,就可以把Sources同时添加到RunLoop的 default、modal、event tracking模式中(下面用一个场景解释这个到底有啥用) |
获取当前线程的RunLoop Mode
NSString* runLoopMode = [[NSRunLoop currentRunLoop] currentMode];
4、界面滚动的时候为什么NSTimer会失效
因为主线程的RunLoop是跑在Default模式的,在主线程中创建一个NSTimer,并制定task,当UIScrollView滚动的时候,主线程的RunLoop就切换到Event Tracking Mode,然而NSTimer默认是添加到Default Mode的,滚动的时候,RunLoop只能执行一个Mode,所以NSTimer的 Mode就退出了。
要解决这种问题,以下有三种方法:
a、把NSTimer添加到另外一个线程中去(非主线程),然后开始运行这个线程的RunLoop。
b、把NSTimer所在的RunLoop Mode改为Event Tracking模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]
//直接把NSTimer添加到Event Tracking模式中
- c、把NSTimer所在的RunLoop Mode改为Common模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//设置Common Modes,把NSTimer同时添加到defaul、modal、Event Tracking模式中
因为RunLoop一次只能执行一个Mode(包含各种Sources),所以把NStimer添加到与滚动事件所在的Mode就行,或者重新开一个线程(不同的RunLoop)。
参考文章
https://hit-alibaba.github.io/interview/iOS/ObjC-Basic/Runloop.html