iOS RunLoop

作用:

  1. 保持程序运行(使线程保持生命)
  2. 处理app的各种事件
  3. 节省CPU资源,提高性能

Runloop和线程:

  1. 每天线程都有唯一的与之对应的RunLoop对象,但这个根的RunLoop可以嵌套子的RunLoops.
    2.主线程的RunLoop已经创建好了,而子线程的RunLoop需要手动的创建(也就是说子线程的RunLoop默认是关闭的,因为有时候开了线程但却没有必要开一个RunLoop,不然反而浪费资源)
    3.RunLoop在第一次获取时创建,在线程结束时销毁.(相当于线程是一个类,Runloop就是这个类的实例变量)

RunLoop的状态(CFRunLoopActivity):

CFRunLoopEntry  //即将进入RunLoop
CFRunLoopBeforeTimers  //即将处理NSTimer
CFRunLoopBeforeSources  //即将处理Sources
CFRunLoopBeforeWaiting  //即将进入休眠状态
CFRunLoopAfterWaiting  //刚从休眠状态醒来
CFRunLoopExit  //即将退出RunLoop

访问和使用RunLoop

  1. 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销毁新池-->线程销毁

  1. 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 顾名思义就是指定时器事件了

你可能感兴趣的:(iOS RunLoop)