一,概念
RunLoop是通过内部维护的来对的一个
事件循环是什么?
事件循环重要通过2点:
没有消息需要处理时,休眠以避免资源占用
用户态 ----> 内核态
当没有消息要处理时,进程会进入休眠状态,把当前线程的控制权交给了内核态有消息需要处理时,立刻被唤醒
用户态 <---- 内核态
科普:内核态和用户态
在我们的程序中 通过
int main(int argc, char * argv[]) {
@autoreleasepool {
}
}
来实现 整个程序的runloop
runloop具备的特点:
1,不断地接收消息
2,对事件进行处理
3,继续等待
二,数据结构
Apple为我们提供了两个RunLoop
------------
NSRunLoop是CFRunLoop的封装,提供了面向对象的API
NSRunLoop位于Foundation当中
CFRunLoop位于CoreFoundation当中
CFRunLoop数据结构
- CFRunLoop
- CFRunLoopMode
- Source/Timer/Observer
CFRunLoop
- Pthread: C级别的线程对象,RunLoop和线程是一一对应的
- currentMode: RunLoop当前所处于的模式
- Modes: 多个mode的集合
- commonModes: 是一个字符串的集合
- commonModeItems: 也是一个集合,包含了多个Observer/Timer/Source
CFRunLoopMode
- name: 对应的是某个RunLoopMode的名称,比如RunLoop.defualt,实际上是别名的字符串
- sources0和sources1是个集合类型的数据结构
- observers和timers是一个数组
Source
source0: 需要手动唤醒线程
source1: 具备唤醒线程的能力
Timer
- timer是基于事件的定时器,其和NSTimer是toll-free bridged的
科普:toll-free bridged
Observer
- 我们可以通过注册一些ObServer来实现对RunLoop一些时间点的监测或观察
观测时间点
-
当Runloop准备启动的时候,会有个kCFRunLoopEntry的回调通知
* 通知观察者Runloop将要对timer事件进行处理 - 通知观察者Runloop将要处理一些Source事件
- 通知观察者Runloop将要进入休眠状态 用户态->内核态
- 从内核态切换到用户态不久时发生
- 当前RunLoop退出
各个数据结构之间的关系
- 线程和RunLoop是一一对应的
- RunLoop可以有n个mode
- 每一个mode都可以有m个Source,Timer,Observer
当RunLoop运行在某个mode上面的时候,如果另一个mode当中的timer事件或者Observer事件发生了回调,那么RunLoop是无法接收到的
这也就是RunLoop当中有多个mode的原因,实际上是起到了屏蔽的作用。
这也就是为什么,我们在滑动TableView的时候,我们的轮播图会暂停播放的原因
CommonMode的特殊性
*
- 实际上,它是同步Source/Timer/Observer到多个Mode中的一种
三,事件循环的实现机制
在RunLoop启动之后,会发送一个通知,告诉观察者即将进入RunLoop,之后RunLoop会将要处理Timer/Source0事件,之后正式进入Source0事件处理,如果有Source1需要处理,系统会通过一条goto语句来处理唤醒时收到的消息,反之线程就会将要休眠,此时发生用户态转换成核心态,再之后系统就会正式休眠,等待唤醒,而等待唤醒的条件有三个:Source1进行唤醒,timer事件发生,或者是外部手动唤醒,当系统被唤醒后,会给观察者再发生一个通知,循环到即将进入RunLoop,当程序被杀死的时候,才会即将退出RunLoop。
RunLoop的核心
当main函数进行处理后,系统会调用mach_msg()函数,于是就发生了系统调用,把控制权交给核心态,然后mach_msg()在一定条件下(Source1,timer,手动唤醒),会把控制权交给用户态,当前app的主运行循环会被唤醒
RunLoopObserver与Autorelease Pool的关系
UIKit 通过 RunLoopObserver 在 RunLoop 两次 Sleep 间对 Autorelease Pool 进行 Pop 和 Push 将这次 Loop 中产生的 Autorelease 对象释放。
RunLoop的机制
主线程 (有 RunLoop 的线程) 几乎所有函数都从以下六个之一的函数调起:
CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
CFRunloop is calling out to an abserver callback function
用于向外部报告 RunLoop 当前状态的更改,框架中很多机制都由 RunLoopObserver 触发,如 CAAnimationCFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
CFRunloop is calling out to a block
消息通知、非延迟的perform、dispatch调用、block回调、KVOCFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
CFRunloop is servicing the main desipatch queueCFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
CFRunloop is calling out to a timer callback function
延迟的perform, 延迟dispatch调用CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
CFRunloop is calling out to a source 0 perform function
处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket。普通函数调用,系统调用CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
CFRunloop is calling out to a source 1 perform function
由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort
四,RunLoop和NSTimer
- NSTimer怎样保证参数的生命周期
NSTimer可以选择是否重复执行,为了保证NSTimer调用的方法中传递的对象生命周期,NSTimer会对外界传递的对象进行一次retain。
如果是一次性调用的NSTimer,会在本次调用完毕之后invalidate掉NSTimer自身,而NSTimer做retain的对象也会被进行一次release。但是如果是多次重复调用的NSTimer,就需要我们自己在某个特定的时刻来invalidate掉NSTimer,这个invalidate的时刻是根据我们代码情况来自己决定的,否则将会一直存在。
下面的方法我们先创建了一个Object对象,然后添加了一个NSTimer(关于NSTimer和Runloop后面再讲),并且进行了一次release,这时Object并没有被释放,而是被NSTimer进行了一次retain,我们通过在Object的dealloc方法中打印就可以知道是否被释放。
在本次NSTimer的Timer所调用方法调用完毕之后,NSTimer会invalidate自身,而Object对象也会被释放。
Object *object = [[Object alloc] init]; [NSTimer scheduledTimerWithTimeInterval:5 target:object selector:@selector(timerAction:) userInfo:nil repeats:NO]; [object release];
而通过下面这种方式创建的Timer就不会被NSTimer自动释放,因为这次调用是重复调用,必须我们显示的进行invalidate,NSTimer才会消失,这时Object对象也就会释放了。
Object *object = [[Object alloc] init]; [NSTimer scheduledTimerWithTimeInterval:5 target:object selector:@selector(timerAction:) userInfo:nil repeats:YES]; [object release];
我们前面做演示的代码创建的NSTimer会默认为我们添加到Runloop的NSDefaultRunLoopMode中,而且由于是在主线程中,所以Runloop是开启的,不需要我们手动打开。
在我们进行多线程编程时,所有的Source都需要添加到Runloop中才能生效,对于我们的NSTimer当然也需要添加到Runloop中才能生效。如果一个Runloop中没有任何Source的话,会立即退出的。而主线程的Runloop在程序运行时,系统就已经为我们添加了很多Source到Runloop中,所以主线程的Runloop是一直存在的,我们可以通过打印MainThread中的Runloop来查看所包含的Source。
下面的代码就没有添加到Runloop中,所以这个NSTimer永远也不会发生作用,这是一份错误的代码示例。
` Object *object = [[Object alloc] init];
NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:object selector:@selector(timerAction:) userInfo:nil repeats:NO];
[object release]; `
在iOS多线程中,每一个线程都有一个Runloop,但是只有主线程的Runloop默认是打开的,其他子线程也就是我们创建的线程的Runloop默认是关闭的,需要我们手动运行。
我们可以通过[NSRunLoop currentRunLoop]来获得当前线程的Runloop,并且调用[runloop addTimer:timer forMode:NSDefaultRunLoopMode]方法将定时器添加到Runloop中,最后一定不要忘记调用Runloop的run方法将当前Runloop开启,否则NSTimer永远也不会运行。
五,补充:AFNetworking中RunLoop的创建
`+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 这里主要是监听某个 port,目的是让这个 Thread 不会回收
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}`
`+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread =
[[NSThread alloc] initWithTarget:self
selector:@selector(networkRequestThreadEntryPoint:)
object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}`