一、Runloop简介
概念:
从字面上来看,
RunLoop
是运行循环的意思,实质上,RunLoop
是一种寄生于线程的消息循环机制,让线程能随时处理事件但不退出,保证线程的存活,而不是线程执行完任务后就消亡。
RunLoop
实际上是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行Event Loop
的逻辑。线程执行了这个函数后,就会一直处于这个函数内部接受消息→等待→处理的循环中,直到达到循环结束条件退出(比如传入 quit 的消息),函数返回。让线程在没有消息处理时休眠以避免资源占用、在有消息到来时立刻被唤醒。
特性:
每一个线程都有一个对应的
RunLoop
对象,主线程默认开启RunLoop
,这能保证应用程序的执行运行,接收并处理各种事件;子线程默认不开启。RunLoop有三种启动方式:
run
、runUntilDate:
、runMode:beforeDate:
。
第一种无条件永远运行RunLoop
并且无法停止,线程永远存在。第二种会在时间到后退出RunLoop
,同样无法主动停止RunLoop
。前两种都是在NSDefaultRunLoopMode
模式下运行。第三种可以选定运行模式,并且在时间到后或者触发了非Timer
的事件后退出。RunLoop能正常运行的条件:运行在添加了至少一个事件源
(Timer/Source/Observer)
的mode
下,否则会自动退出。经过NSRunLoop
封装之后,只可以往mode
中添加两类事件:NSPort
和NSTimer
。
五个类:
- CFRunLoopRef:
RunLoop
对象- CFRunLoopModeRef:
RunLoop
的运行模式- CFRunLoopSourceRef:事件产生的地方
- CFRunLoopTimerRef:定时器
(NSTimer)
- CFRunLoopObserverRef:观察者,回调
Runloop
状态
iOS 中的RunLoop:
在iOS系统中,提供了两种
RunLoop
对象:NSRunLoop
和CFRunLoopRef
。
CFRunLoopRef
是在CoreFoundation
框架内,它提供了面向过程的纯C
函数API
。
NSRunLoop
是基于CFRunLoopRef
的封装,提供了面向对象的API
。两者最主要的区别在于:
NSRunLoop
是非线程安全的,意味着只能在当前线程中获取线程对应的RunLoop
(主线程RunLoop
除外),而CFRunLoopRef
是线程安全的。
我们不能够手动创建RunLoop,系统为我们提供了获取的方法
[NSRunLoop currentRunLoop]; //获取当前线程的RunLoop
[NSRunLoop mainRunLoop]; //获取主线程的RunLoop
CFRunLoopGetMain(); //获取当前线程的RunLoop
CFRunLoopGetCurrent(); //获取主线程的RunLoop
作用:
- 让主线程一直处于收发消息的状态,不会自动结束,保证了程序的运行
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省
CPU
资源,提高程序性能
二、Runloop的模式和状态
- 一个
Runloop
对应一条线程 。[1]
- 一个
Runloop
里面可以有多个mode
,每一个Mode
又可以包含多个mode item
。source/timer/observer
被统称为mode item
,不同的mode
下mode item
互不影响。- 同一个时刻,
RunLoop
只能是在一个mode
上面的运行。如果需要切换mode
,只能是退出currentMode
,切换到指定的mode
。
1、RunLoop的模式
总共是有五种
CFRunLoopMode
:(前2种常见的及后3种不常见的、系统的)
kCFRunLoopDefaultMode
:默认模式,主线程是在这个运行模式下运行UITrackingRunLoopMode
:界面跟踪mode
,用于ScrollView
追踪触摸滑动,保证界面滑动时不受其他mode
影响(当一旦滚动屏幕,就会自动切换到这个模式)UIInitializationRunLoopMode
:在刚启动App时第进入的第一个mode,启动完成后就不再使用GSEventReceiveRunLoopMode
:接受系统内部事件,通常用不到kCFRunLoopCommonModes
:伪mode
,若干个mode
的集合,是同步Source/Timer/Observer
到多个mode
中的一种解决方案
2、RunLoop的状态
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
};
三、RunLoop的运行逻辑
总结成一句话就是:
RunLoop
的运行逻辑就是do-while
循环下运用观察者模式(或者说是消息发送),根据7种状态的变化,处理事件输入源和定时器。
四、面试题
1、什么是RunLoop?
- 从字面上理解,
RunLoop
是个运行循环。 - 其实它内部就是do-while循环,在这个循环内部不断的处理各种任务(比如
Source\Timer\Observer
) - 一个线程对应一个
RunLoop
,主线程的RunLoop
默认已经启动,子线程的RunLoop
需要手动启动(调用run
方法)
RunLoop只能选择一个mode
启动,如果当前mode
中没有任何Soure\Timer\Observer
,那么就直接退出RunLoop
2、在开发中如何使用RunLoop?什么应用场景?
场景一:解决NSTimer在
ScrollView
滑动的时候失效的问题static int count = 0; [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES >block:^(NSTimer * _Nonnull timer) { NSLog(@"count == %d",count++); }];
原因:
NSTimer
在RunLoop
的mode
是NSDefaultRunLoopMode
中的,当滑动的时候RunLoop会切换到UITrackingRunLoopMode
,所以NSTimer
会失效。解决:
将Timer
添加到NSRunLoopCommonModes
模式下面NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"count == %d",count++); }]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
-
线程和runloop是一一对应的关系,内部是一个字典,key为线程,value为runloop ↩