Runloop 其实是一种很多种语言都有的机制, 比如 Node.js 的事件处理, windows 的消息循环, 那么 runloop 到底是什么呢?
你可以这么回答, runloop 就是一个事件循环, 用来不停的调配工作和处理输入事件, 保持程序持续运行, 在没有工作的时候休眠,节省 CPU 资源,提高性能, runloop可以让线程保持活跃状态, 随时接受处理事件, 如果没有 runloop 线程就会在执行完当前事件就会退出, 所以在子线程中使用定时器的时候, 一定要记得开启 runloop, 只有开启 runloop 线程才会一直保持活跃状态不会退出, 不然定时器就 GG 了
首先怎么创建 runloop, 苹果只提供了获取 runloop的方法, 在第一次获取时自动创建, 在线程结束时销毁
[NSRunLoop currentRunLoop] 或 [NSRunLoop mainRunLoop]
当然还有 C 语言的接口 self.runloop = CFRunloopGetCurrent() CFRunloopRun()
创建一个子线程就默认开启 runloop 吗?
并不是这样的, 主线程的 runloop 系统以及为我们创建并且运行, 不然可以想象我们的 APP 就启动不起来或者马上挂掉了, 也就是 runloop 使我们的主线程一直活跃 随时接受 UI 事件等, 那么自己创建的线程如何开启呢?
RunLoop 想要跑起来,必须有 Mode 对象支持,而 Mode 里面必须有(NSSet *)Source
、 (NSArray *)Timer
,源和定时器。
在你的子线程中获取当前 runloop NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
默认的 Mode是kCFRunLoopDefaultMode
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
这三种方式启动的runloop 如果没有输入源或者 timer 附加与 runloop 上, runloop 就会立刻退出, 如何保证他不退出呢 , 没有事件就休眠, 有事件就起来处理,可以使用下面的方法
需要添加 port 设置 mode,
[runLoop addPort:[NSMachPort port] forMode:NSRunLoopCommonModes];
然后 [runloop run] ,这样就可以做到常驻线程, 该线程与主线程生命周期相同, 适合做一些在后台一直运行的事情, 比如监听是否联网等等
在自己创建的子线程中,最好把所有的任务都放入一个自己创建的 autorealesePool 中, 因为在 runloop 进入睡眠之前会清掉自动释放池,并创建一个新的自动释放池
如何退出 runloop ?
如果你开启了 runloop , 则子线程一直运行, 想要结束子线程结束 runloop CFRunLoopStop(CFRunLoopGetCurrent())
对于这几种 mode:
1. kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
2. UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
4: GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
5: kCFRunLoopCommonModes: 这是一个占位的 Mode,没有实际作用。
NSRunLoopCommonModes并不是一种Mode,而是一种特殊的标记,包含三种mode(kCFRunLoopDefaultMode、NSTaskDeathCheckMode、UITrackingRunLoopMode),添加到NSRunLoopCommonModes中的还没有执行的任务,会在mode切换时,再次添加到当前的mode中,这样就能保证不管当前runloop切换到哪一个mode,任务都能正常执行。并且被添加到NSRunLoopCommonModes中的任务会存储在runloop 的commonModeItems中。
所以就有一个小小的例子, 当一个滚动视图的头视图添加一个定时轮播器的时候 , 发现当你滑动下面的列表时 上面的轮播器不会播放了,为什么?
主要的原因就是当我们滑动scrollView时,主线程的RunLoop 会切换到UITrackingRunLoopMode这个Mode,执行的也是UITrackingRunLoopMode下的任务(Mode中的item),而timer 是添加在NSDefaultRunLoopMode下的,所以timer任务并不会执行,只有当UITrackingRunLoopMode的任务执行完毕,runloop切换到NSDefaultRunLoopMode后,才会继续执行timer。
怎么解决 ? 把 mode设置为 Common 即可
- (void)timerTest
{
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[timer fire];
}
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerUpdate) userInfo:nil repeats:YES]; // 需要注意的是 这种定时器的创建是已经加入了 NSRunloop 中 并且是 DefaultMode
当然了如果你是在子线程中添加的定时器就不会出现滑动定时器不运行的状态, 设为 default 也没事, 因为主线程的 runloop 和子线程是相互独立的,CPU会在多个线程间切换来执行任务,呈现出多个线程同时执行的效果。执行的任务其实就是RunLoop去各个Mode里执行各个item。因为RunLoop是独立的两个,相互不会影响,所以在子线程添加timer,滑动视图时,timer能正常运行,但是需要的注意的事,一定要保证 runloop 一直运行