1、是啥?
处理事件的循环,简单来说它就是用来处理事件的一个对象,它给我们提供了两个对象,一个是基于CoreFoundation框架的CFRunloopRef,相对比较底层一点,另外一个就是基于CFRunloopRef封装的NSRunloop,我们通常通过NSRunlop完成相关操作;
2、有什么作用?
- 保持线程的鲜活;(让线程不会执行一次任务后就退出结束)
- 在有任务时工作,
没有时休眠,减少CPU的压力;(在有输入源工作,没有时会sleep) - 处理各种事件源;
3、它和线程有什么关系呢?
runloop和线程是一一对应的关系,也就是说一个线程对应一个runloop,程序启动后系统会创建一个全局的字典保存runloop,线程是key,runloop是对应的value,而且runloop采用类似懒加载的形式,子线程的runloop第一次去获取才会去创建,也就是根据线程去全局的字典中去找,找不到创建,但是需要注意的是主线程的runloop是程序启动就会自动获取的,CFRunloopRef中可以用CFRunLoopGetMain() 和 CFRunLoopGetCurrent()获取.
4、RunLoopMode和ModeItem
Runloop启动时需要指定一个RunloopMode才能运行,而且一个Runloop可以有多个RunloopMode,如果你要切换模式,则需要先退出当前的RunloopMode,一个RunloopMode又由多个modeItem组成,但是至少得有一个modeItem;下面介绍一下modeItem:
1. modeItem:简单来说既是源头,既是触发Runloop干事的源;
2. runlop中的源有输入源Input Source(各种触发事件),定时源Timer Source(定时器),观察者(Observer);
3. runloopMode可以认为是如何分配安排它内部上面各种源的执行方式和顺序;
<1> 输入源Input Source
(1)基于端口输入源(Source1):它是内核驱动的,苹果官方的介绍是这样的:
也就是说我们根本不需要创建source1,只需要创建一个端口对象NSPort,然后将这个port添加到当前runloop就好,当我们向该端口发送消息时,端口对象会为我们创建source1,主动唤醒runloop并触发相应回调;我们可以使用CFMachPortRef,CFMessagePortRef或CFSocketRef创建合适的端口对象;
(2)自定义输入源(Source0):
让Source0执行回调需要手动标记Source0为待处理状态,还需要呼醒Source0所在的RunLoop
<2>定时源Timer Source:
<3>观察者(Observer):
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Loop
};
我们可以手动添加observe监听runloop的状态做相应的回调:
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFStringRef runLoopMode = kCFRunLoopDefaultMode;
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler
(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {
});
CFRunLoopAddObserver(runLoop, observer, runLoopMode);
5、runloop的内部逻辑:
- 首先根据modeName找到对应mode,modeitems为空,直接return;
- 通知observers即将进入loop,(Entry)创建自动释放池autoreleasePool;
- 通知observers即将处理Timer;
- 通知observers即将处理Source0;
- 执行Source0加入的block回调;
- 如果有Source1的消息,需要立刻处理这个Source1;所以直接跳转到10;
- 如果没有待处理的消息,通知observers即将进入休眠(sleep);(销毁自动释放池并创建新的自动释放池)
- 如果有以下的情况发生,通知observers runloop即将被唤醒;
a. source1事件;
b. 一个Timer时间到了;
c. runloop启动时设置的最大超过时间;
d. 手动唤醒;调用CFRunLoopSourceSignal(source)并CFRunLoopWakeUp(runloop)唤醒runloop; - 通知observers即将被唤醒;
- 处理唤醒时收到的消息;跳转到3;
- 如果有以下的情况发生,通知observers即将退出;
<1>设置了runloop最大的超时时间;
<2>modeitems为空;
<3>手动停止,CFRunLoopStop();
6、runloop的启动和关闭;
(1)runloop的启动:
run:使用run启动runloop时CFRunLoopStop()是无法退出的,如果想退出那么只能让modeitem里面空;但是使用CFRunLoopRun()是可以直接用CFRunLoopStop()退出的;
runUntilDate:runloop运行直到某个时间点结束;
runMode:beforeDate:在某个时间点之前在,某个RunLoopMode下运行runloop;
(2)runloop的关闭:
设置超时时间:以上的二三中情况;
手动结束CFRunLoopStop():以上的第一种情况;
7、runloop的应用:
<1>AFNetWorking;
下面是每个runloop文章中必提的一段代码:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[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;
}
这段代码我们需要注意两个问题:
1,为什么这里的请求创建的子线程需要用单例?
2,为什么在获取当前线程的runloop的时候向它添加一个NSMachPort并run?
答:AFN是对于NSURLConnection的封装,但是请求的结果是异步返回的,如果不给该线程添加runloop,那么请求结束后就会推出,这时候我们就需要给他添加runloop保证线程鲜活,添加NSMachPort是因为runloop的启动是必须有一个modeitem的,所以添加了一个source进去,挂起该线程;至于用单例是为了防止不断创建线程导致内存的一直涨;
<2>UITableView-FDTemplateLayoutCell;
- FDTemplateLayoutCell是百度团队开发一个高度预缓存的框架;它会在页面处于空闲状态时计算并缓存cell的高度,当用户正在滑动列表时显然不应该执行计算任务影响滑动体验;对于cell可变的界面比如聊天优化是非常客观的;
- 它的原理也是利用runloop;它该当前的runloop注册了一个observe,监听runloop即将休眠的时候,也就是上面的步骤7时,runloop处理了所有的source事件后即将进入睡眠时,监听到后回调计算cell的高度;
<3>定时器滑动同时;
<4>GCD;
<5>PerformSelecter;
参考本章:
1,http://www.jianshu.com/u/3e55748920d2
2,https://blog.ibireme.com/2015/05/18/runloop/