作用:
- 保持程序运行(使线程保持生命)
- 处理app的各种事件
- 节省CPU资源,提高性能
Runloop和线程:
- 每天线程都有唯一的与之对应的RunLoop对象,但这个根的RunLoop可以嵌套子的RunLoops.
2.主线程的RunLoop已经创建好了,而子线程的RunLoop需要手动的创建(也就是说子线程的RunLoop默认是关闭的,因为有时候开了线程但却没有必要开一个RunLoop,不然反而浪费资源)
3.RunLoop在第一次获取时创建,在线程结束时销毁.(相当于线程是一个类,Runloop就是这个类的实例变量)
RunLoop的状态(CFRunLoopActivity):
CFRunLoopEntry //即将进入RunLoop
CFRunLoopBeforeTimers //即将处理NSTimer
CFRunLoopBeforeSources //即将处理Sources
CFRunLoopBeforeWaiting //即将进入休眠状态
CFRunLoopAfterWaiting //刚从休眠状态醒来
CFRunLoopExit //即将退出RunLoop
访问和使用RunLoop
- Foundation ---> NSRunLoop 对CFRunLoopRef的封装,使用方便
2.Core Foundation ---> CFRunLoopRef C语言编写,开源,更加底层,性能更高
获取当前线程的RunLoop对象:
[NSRunLoop currentRunLoop];
CFRunLoopGetCurrent();
获取主线程的RunLoop对象:
[NSRunLoop mainRunLoop];
CFRunLoopGetMain();
RunLoop相关类:
CFRunLoopRef: 一个RunLoop包含若干个Mode.
CFRunLoopModeRef: 代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
CFRunLoopSourceRef: 事件源, 分两种.
source0: 自定义,不基于Port;
source1:基于port,系统
CFRunLoopTimerRef: 基于事件的触发器. NSTimer会受到runloop的mode影响. GCD的定时器不受runloop的mode影响(dispatch_after)
CFRunLoopObserverRef: 观察者,监听RunLoop的状态变化
每次调用RunLoop的主函数时,只能指定其中一个Mode,这个Mode被称作CurrentMode. 若需要切换Mode,只能退出loop再重新指定一个Mode进入.
主要为了分隔开不同组的Source/Timer/Observer 互不影响
五个Mode
kCFRunLoopDefaultMode //apple的默认Mode,通常主线程是在这个Mode下运行的
UITrackingRunLoopMode //界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他mode影响
UIInitializationRunLoopMode //在刚启动App时进入的第一个Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode //接受系统事件的内部Mode,通常用不到
kCFRunLoopCommonModes //这是一个占位用的Mode,不是真正的mode
( 其实如果设置mode为kCFRunLoopCommonModes,即支持DefaultMode,又支持TrackingMode)
苹果公开提供的只有NSDefaultRunLoopMode和NSCommonRunLoopMode.
启动RunLoop
[[NSRunLoop currentRunLoop] run];
或者
self.runloop = CFRunLoopGetCurrent();
CFRunLoopRun();
其中NSRunLoop的启动方式又分为三种:
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
这三种方式无论通过哪一种方式启动runloop,如果没有一个输入源或者timer附加于runloop上,runloop就会立刻退出。
(1) 第一种方式,runloop会一直运行下去,在此期间会处理来自输入源的数据,并且会在NSDefaultRunLoopMode模式下重复调用runMode:beforeDate:方法;
(2) 第二种方式,可以设置超时时间,在超时时间到达之前,runloop会一直运行,在此期间runloop会处理来自输入源的数据,并且也会在NSDefaultRunLoopMode模式下重复调用runMode:beforeDate:方法;
(3) 第三种方式,runloop会运行一次,超时时间到达或者第一个input source被处理,则runloop就会退出。
前两种启动方式会重复调用runMode:beforeDate:方法。
关闭RunLoop
第一种启动方式的退出方法
文档说,如果想退出runloop,不应该使用第一种启动方式来启动runloop。
如果runloop没有input sources或者附加的timer,runloop就会退出。
虽然这样可以将runloop退出,但是苹果并不建议我们这么做,因为系统内部有可能会在当前线程的runloop中添加一些输入源,所以通过手动移除input source或者timer这种方式,并不能保证runloop一定会退出。
第二种启动方式runUntilDate:
可以通过设置超时时间来退出runloop。
第三种启动方式runMode:beforeDate:
通过这种方式启动,runloop会运行一次,当超时时间到达或者第一个输入源被处理,runloop就会退出。
如果我们想控制runloop的退出时机,而不是在处理完一个输入源事件之后就退出,那么就要重复调用runMode:beforeDate:,
具体可以参考苹果文档给出的方案,如下:
NSRunLoop *myLoop = [NSRunLoop currentRunLoop];
myPort = (NSMachPort *)[NSMachPort port];
[myLoop addPort:_port forMode:NSDefaultRunLoopMode];
BOOL isLoopRunning = YES; // global
while (isLoopRunning && [myLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
//关闭runloop的地方
- (void)quitLoop
{
isLoopRunning = NO;
CFRunLoopStop(CFRunLoopGetCurrent());
}
总之:
如果不想退出runloop可以使用第一种方式启动runloop;
使用第二种方式启动runloop,可以通过设置超时时间来退出;
使用第三种方式启动runloop,可以通过设置超时时间或者使用CFRunLoopStop方法来退出。
RunLoop的退出条件
app退出
线程关闭
设置的最大时间到期
modeitem为空
RunLoop和自动释放池以及线程相关
RunLoop寄生于线程,一个线程只能有唯一对应的runloop,但根runloop可以嵌套子的runloops.
自动释放池寄生于RunLoop:
程序启动后主线程注册了两个observer监听runloop的进出和睡觉,一个最高优先级observer监听Entry状态,一个最低优先级的observe监听BeforeWaiting状态和Exit状态
(线程)创建-->runloop将进入-->最高优先级的observer创建释放池-->runloop将进入睡眠状态-->最低优先级的observer销毁旧池,创建新池-->runloop退出-->最低优先级的observer销毁新池-->线程销毁
- AutoreleasePool 创建是在一个RunLoop开始之前(Push)
2.AutoreleasePool 释放是在一个RunLoop事件即将结束之前(POP)
3.AutoreleasePool 里的Autorelease对象的加入是在RunLoop事件中
4.AutoreleasePool 里的Autorelease对象的释放是在AutoreleasePool释放时
总结:
每一个线程都有与之对应的RunLoop且是唯一的与之对应的RunLoop对象,但是根的RunLoop可以嵌套子的RunLoops
RunLoop 寄生于线程
AutoreleasePool自动释放池寄生于RunLoop
Autorelease对象由AutoreleasePool管理
RunLoop输入事件来源:
RunLoop接收输入事件来自两种不同的来源: 输入源(input source)和定时源(timer source)
Input Source 分为三类:
1.Port-Based Source: 系统底层的Port事件,例如CFSocketRef,在底层应用层基本用不到
2.Custom Input Source: 用于手动创建的Source
3.Cocoa perform Selector Source: Cocoa 提供的performSelector 系统方法, 也是一种事件源
Timer Source 顾名思义就是指定时器事件了