弄清楚NSRunLoop确实需要花时间,这个类的概念和模式似乎是Apple的平台独有(iOS+MacOSX),很难彻底搞懂(iOS没开源)。官网的解释是说run loop可以用于处理异步事件,很抽象的说法。
一、认识NSRunLoop
NSRunLoop是消息机制的处理模式,其作用在于:有事情做的时候使的当前NSRunLoop的线程工作,没有事情做让当前NSRunLoop的线程休眠。NSRunLoop就是一直在循环检测,从线程start到线程end,检测inputsource(如点击,双击等操作)同步事件,检测timesource同步事件,检测到输入源会执行处理函数,首先会产生通知,corefunction向线程添加runloop observers来监听事件,意在监听事件发生时来做处理。
在单线程的app中,不需要注意Run Loop,但不代表没有。程序启动时,系统已经在主线程中加入了Run Loop。它保证了我们的主线程在运行起来后,就处于一种“等待”的状态(而不像一些命令行程序一样运行一次就结束了),这个时候如果有接收到的事件(Timer的定时到了或是其他线程的消息),就会执行任务,否则就处于休眠状态。
注意: 所有线程都自动创建一个RunLoop, 在线程内通过 [NSRunLoop currentRunLoop] 获得当前线程的RunLoop.
为了证明它确实是使用的RunLoop, 我将程序在响应鼠标单击按钮时的调用栈显示如下:
了解了NSRunLoop的作用后,我们再来看一下它的应用范围:
由上图我们可知,NSRunLoop响应两种类型的消息: Input sources 和 Timer sources. 就是前面我们讲到的,它在等待响应消息时,只处理这两种消息源。
1.Time Source. Timer sources deliver synchronous events, occurring at a scheduled time or repeating interval.
苹果文档中有句话需要注意,Timer sources deliver events to their handler routines but do not cause the run loop to exit.
创建NSTimer添加到run loop中的时候,这里需要注意的是,NSTimer默认是处于NSDefaultRunloopMode,这也就可以解释为什么如果你在你的控制器中添加了一个timer定时刷新你的界面,而你在拖动视图的时候timer不回fire,因为这个时候你的runloop 是NSEventTrackingRunloopMode,在这个mode下timer不回fire。
2.input source input source 主要是一些异步的事件,比如来自其它线程或者其它app的消息。
input source 传递异步事件到其对应的处理函数,并且使runUntilDate(与线程相关联的runloop对象调用)返回。
为了能够处理input sourcr,run loops 产生notifications.通过注册成run-loop observers可以接受到这些通知(通过Core Foundation 来注册observers).
二、使用NSRunLoop
● Run Loop和线程的关系:
1. 主线程的run loop默认是启动的,用于接收各种输入sources
2. 对第二线程来说,run loop默认是没有启动的(但是确实是存在的),如果你需要更多的线程交互则可以手动配置和启动,如果线程执行一个长时间已确定的任务则不需要。
● Run Loop什么情况下使用:
1. 使用ports 或 input sources 和其他线程通信 // 不了解
2. 在线程中使用timers // 如果不启动run loop,timer的事件是不会响应的
3. 在Cocoa 应用中使用performSelector...方法 // 应该是performSelector...这种方法会启动一个线程并启动run loop吧
4. 让线程执行一个周期性的任务
● 跟NSRunLoop密切相关的知识点
1. timer的创建和释放必须在同一线程中。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
此方法会retain timer对象的引用计数。
2. NSTimer要添加到RunLoop中才会有作用 NSTimer加到了RunLoop中但迟迟的不触发事件,可能是因为线程没有启动。
三、NSRunLoop几个常用的方法:
+ (NSRunLoop *)currentRunLoop; //获得当前线程的run loop
+ (NSRunLoop *)mainRunLoop; //获得主线程的run loop
- (void)run; //进入处理事件循环,如果没有事件则立刻返回。注意:主线程上调用这个方法会导致无法返回(进入无限循环,虽然不会阻塞主线程),因为主线程一般总是会有事件处理。
- (void)runUntilDate:(NSDate *)limitDate; //同run方法,增加超时参数limitDate,避免进入无限循环。使用在UI线程(亦即主线程)上,可以达到暂停的效果。
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate; //等待消息处理,好比在PC终端窗口上等待键盘输入。一旦有合适事件(mode相当于定义了事件的类型)被处理了,则立刻返回;类同run方法,如果没有事件处理也立刻返回;有否事件处理由返回布尔值判断。同样limitDate为超时参数。
- (void)acceptInputForMode:(NSString *)mode beforeDate:(NSDate *)limitDate; //似乎和runMode:差不多(测试过是这种结果,但确定是否有其它特殊情况下的不同),没有BOOL返回值。
参考文章如下 :
2. csdn-Topurce
3. csdn-技术内幕
4. 亚庆的 Blog