什么是Runloop
?
Runloop
是通过内部维护的事件循环来对事件和消息进行管理的一种机制。当没有消息需要处理的时候,线程进入休眠以避免占用资源,有消息需要处理时,立即被唤醒。
runloop
循环不是单独的do-while
循环,而是发生一个用户态到内核态切换,以及内核态到用户态切换。它维护的事件循环可以用来不断的处理消息和事件,当没有消息和事件需要处理时会从用户态切换到内核态,由此可以用来休眠线程,避免资源占用。当有消息需要处理时会从内核态切换到用户态,当前线程会被唤醒,所以状态切换才是runloop
的关键。
iOS
中提供了两套Runloop
接口,一个是NSRunLoop
基于Objective-C
,在Foundation
框架中,另一个是CFRunLoopRef
基于C
,在CoreFoundation
中。而NSRunLoop
是对CFRunLoopRef
的封装,两者接口基本都是对应的。CFRunLoopRef runloop = [nsrunloop getCFRunLoop]
可以获取对应的CFRunLoopRef
。通过一个表格来对比一下,后面我们将通过对CFRunloop
的解读来更深刻的理解Runloop
。
特征 | NSRunLoop | CFRunLoopRef |
---|---|---|
所属框架 | Objective-C/Foundation | C/CoreFoundation |
获取Runloop | [NSRunLoop currentRunLoop] [NSRunLoop mainRunLoop] |
CFRunLoopGetCurrent() CFRunLoopGetMain() |
Source事件 | addPort:forMode: removePort:forMode: |
CFRunLoopAddSource(...) CFRunLoopRemoveSource(...) |
Timer事件 | addTimer:forMode: | CFRunLoopAddTimer(...) |
Observer事件 | CFRunLoopAddObserver(...) CFRunLoopRemoveObserver(...) |
|
run | run runUntilDate: runMode:beforeDate: |
CFRunLoopRun() CFRunLoopRunInMode(...) CFRunLoopRunSpecific(...) |
1. __CFRunLoop相关数据结构
struct __CFRunLoop {
...
pthread_t _pthread;//对应的线程
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
...
};
__CFRunLoop
是runloop
本身:typedef struct __CFRunLoop *CFRunLoopRef
。__CFRunLoop
对应多个__CFRunLoopMode
。
struct __CFRunLoopMode {
...
CFStringRef _name;
CFMutableSetRef _sources0;//
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
...
};
__CFRunLoopMode
是runloop的运行模式:typedef struct __CFRunLoopMode *CFRunLoopModeRef
。每一个__CFRunLoopMode
又包含多个_sources0、_sources1、_observers、_timers事件。_sources0:非基于Port的,也就是用户主动发出的事件。_sources1:基于Port的,也就是系统内部的消息事件。_observers:观察者。
_timers:定时器事件。
系统默认注册了5中类型的Mode
系统注册的mode | 说明 |
---|---|
kCFRunLoopDefaultMode | App的默认Mode,通常主线程是在这个Mode下运行 |
UITrackingRunLoopMode | 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响 |
UIInitializationRunLoopMode | 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用 |
GSEventReceiveRunLoopMode | 接受系统事件的内部 Mode,通常用不到 |
kCFRunLoopCommonModes | 这是一个占位用的Mode,不是一种真正的Mode |
主线程默认运行在kCFRunLoopDefaultMode
下,滑动scrollView
,就变成了UITrackingRunLoopMode
,手指离开又变成了kCFRunLoopDefaultMode
。
相关的类的成员变量与关系:
如上图时Runloop中用到的基础结构,再对应关系方面:一个__CFRunLoop实例可以包含多个__CFRunLoopMode;一个__CFRunLoopMode又包含多个CFRunLoopSourceRef、CFRunLoopObserverRef、CFRunLoopTimerRef事件。一个Runloop要想跑起来,内部必须要有一个Mode,并且这个Mode里边必须包含一个Source/Observer/Timer事件。
CFRunLoop的状态:
名称 | 说明 |
---|---|
kCFRunLoopEntry | 即将进入runloop |
kCFRunLoopBeforeTimers | 即将处理timer事件 |
kCFRunLoopBeforeSources | 即将处理source事件 |
kCFRunLoopBeforeWaiting | 即将进入睡眠 |
kCFRunLoopAfterWaiting | 被唤醒 |
kCFRunLoopExit | runloop退出 |
2._CFRunLoop的创建、运行、退出
-创建
[NSRunLoop mainRunLoop]
对应底层的CFRunLoopGetMain()
,[NSRunLoop currentRunLoop]
对应底层的CFRunLoopGetCurrent()
,内部都是通过CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t)
获取的runloop
,分别传入pthread_main_thread_np()
和pthread_self()
也就是主线程和当前线程的id。
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
//如果外部传入无效的0,则将主线程ID赋值给t。
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
//如果__CFRunLoops为空,则创建主线程对应的runloop
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//__CFRunLoopCreate做线程的初始化
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 将mainLoop保存到dict中,以线程id为key,mainLoop为value
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
//将dict中的内容复制到__CFRunLoops地址上
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
//通过线程id获取runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
//如果获取到的为空,则直接创建
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//如果创建后还不能获取到则使用刚才创建的,并将newLoop保存到__CFRunLoops中
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
//如果获取的是当前线程的
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
在__CFRunLoopCreate
中通过_CFRuntimeCreateInstance
实例华再进行其他变量的一些初始化。其中loop->_pthread = t;
将线程id
绑定到了runloop
上。runloop
通过线程id
去查找,如果没有则进行创建并将线程id
绑定到runloop
上,通过这个规则我们知道:线程与runloop
一一对应;线程不一定都有runloop
,首个runloop
创建时会检查__CFRunLoops
是否为空,为空则先创建主线程的runloop
,再创建指定线程的runloop
。
-运行
启动Runloop
,调用CFRunLoopRun()
即可,Runloop
进入运行循环,运行状态只要不是kCFRunLoopRunStopped
和kCFRunLoopRunFinished
就会一直运行下去不退出。
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
//将runloop的运行状态切换到指定的Mode
//代码较长,只列出重要步骤的
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
...
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//如果没有获取到mode或者mode中的事件为空(无sources0/sources1等)返回kCFRunLoopRunFinished
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
return kCFRunLoopRunFinished;
}
//保存previousPerRun、previousMode
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
//通知Observer即将进入循环
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//通知Observer即将退出循环
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
//恢复previousPerRun,previousMode
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
return result;
}
3.Runloop的使用
3.1.main
函数为何能保持不退出?
在main
函数中,会调用UIApplicationMain
函数,在内部会启动主线程的Runloop
,可以不断的接收消息,比如点击屏幕事件,滑动列表以及处理网络请求的返回等接收消息后对事件进行处理,处理完之后,就会继续等待。
3.2.NSTimer相关案例
案例1:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
我们主线程执行如下代码,我们Timer能够正常运行,但是如果我们在进行scrollView滑动的时候定时器会停止,这是什么原因呢?在新开子线程调用它运行不起来,这是什么原因呢?
这里我们需要明白:1.scheduledTimerWithTimeInterval:
方法会自动把当前初始化的Timer
加入到currentRunLoop
的kCFRunLoopDefaultMode
模式下,主线程的runloop
已经在run
状态了,所以定时器会立即启动。如果手动滑动scrollView
,则主线程的runloop
的状态切换为UITrackingRunLoopMode
模式了,添加在kCFRunLoopDefaultMode
模式的Timer
自然就没有回调了。解决办法:将Timer
手动添加到UITrackingRunLoopMode
模式或者kCFRunLoopCommonModes
模式即可。
如果在新开的子线程执行上面的代码,由于新开的子线程并不会主动创建runloop
,所以定时器自然运行不起来。解决办法:手动将Timer
加入到currentRunLoop
的kCFRunLoopCommonModes
模式,并且执行run
方法。
3.3监听runloop的运行状态
-(void)addObserver{
/*1.创建监听者
第一个参数:怎么分配存储空间
第二个参数:要监听的状态 。kCFRunLoopAllActivities表示所有的状态
第三个参数:是否持续监听
第四个参数:优先级 总是传0
第五个参数:当状态改变时候的回调
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入runloop"); break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer事件"); break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理source事件");break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入睡眠"); break;
case kCFRunLoopAfterWaiting:
NSLog(@"被唤醒"); break;
case kCFRunLoopExit:
NSLog(@"runloop退出");break;
default:
break;
}
});
/*2.添加监听者
第一个参数:要监听哪个runloop
第二个参数:观察者
第三个参数:运行模式
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
}
3.4常驻线程
一个线程要想跑起来,则需要至少一个mode,一个事件。所以我们可以使用Timer或者Source事件。
第一步创建一个新的线程
- (void)createThread {
self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(task1) object:nil];
[self.thread start];
}
第二步在新开的线程添加Timer或者Source事件。
- (void)task1{
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}
第三部测试线程是否正常运行。可以通过点击等连续触发,也可以在主线程多次调用test来测试
- (void)test {
[self performSelector:@selector(task2) onThread:self.thread withObject:nil waitUntilDone:YES];
}
-(void)task2{
NSLog(@"task2---%@",[NSThread currentThread]);
}