Runloop
什么是Runloop
顾名思义,Runloop就是指运行循环,在程序运行期间循环的做一些事情。
应用范畴:
- 定时器(Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
如果没有Runloop,main函数执行完之后就会退出,而有了Runloop之后,程序并不会马上退出,而是保持运行状态。
Runloop的基本作用:
- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
- ……
Runloop对象
iOS中有2套API来访问和使用 RunLoop
- Foundation:NSRunLoop
- Core Foundation:CFRunLoopRef
NSRunLoop 和 CFRunLoopRef 都代表着 RunLoop 对象
NSRunLoop 是基于 CFRunLoopRef 的一层OC封装
CFRunLoopRef是开源的,开源地址
Runloop与线程
-
每条线程都有唯一的一个与之对应的 RunLoop 对象
-
RunLoop 保存在一个全局的 Dictionary 里,线程作为 key,RunLoop 作为 value
-
线程刚创建时并没有 RunLoop 对象,RunLoop 会在第一次获取它时创建
-
RunLoop 会在线程结束时销毁
-
主线程的 RunLoop 已经自动获取(创建),子线程默认没有开启 RunLoop
// Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
// Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
dispatch_async(dispatch_get_main_queue(), ^{
// 子线程默认是没有创建Runloop的,在首次获取的时候,自动创建
[NSRunLoop currentRunLoop];
});
复制代码
Runloop相关的类
Core Foundation中关于RunLoop的5个类
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
CFRunLoopModeRef
-
CFRunLoopModeRef 代表 RunLoop 的运行模式
-
一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个Source0/Source1/Timer/Observer
-
RunLoop 启动时只能选择其中一个 Mode,作为 currentMode
-
如果需要切换 Mode,只能退出当前 Loop,再重新选择一个 Mode 进入
- 不同组的 Source0/Source1/Timer/Observer 能分隔开来,互不影响
-
如果 Mode 里没有任何 Source0/Source1/Timer/Observer,RunLoop 会立马退出
常见的2种Mode:
-
kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
-
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
Mode内资源:
- Source0
- 触摸事件处理
performSelector:onThread:
- Source1
- 基于Port的线程间通信
- 系统事件捕捉,之后交给Source0处理
- Timers
- NSTimer
performSelector:withObject:afterDelay:
- Observers
- 用于监听RunLoop的状态
- UI刷新(BeforeWaiting)
- Autorelease pool(BeforeWaiting)
CFRunLoopObserverRef
我们可以对Runloop进行监听,观察Runloop的运行状态。
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Runloop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出Runloop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
// 添加Observer监听RunLoop的所有状态
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
// 创建Observer
CFRunLoopObserverRef observer1 = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer1, kCFRunLoopCommonModes);
// 释放
CFRelease(observer1);
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@", mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit: {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit - %@", mode);
CFRelease(mode);
break;
}
default:
break;
}
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
复制代码
Runloop的运行逻辑
Runloop在实际开发中的应用
1.控制线程生命周期(线程保活)
@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end
void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[MJThread alloc] initWithBlock:^{
NSLog(@"%@----begin----", [NSThread currentThread]);
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
CFRunLoopObserverRef observer1 = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer1, kCFRunLoopCommonModes);
// 释放
CFRelease(observer1);
// 这里要先判断self是否已经被释放了
while (weakSelf && !weakSelf.isStoped) {
NSLog(@"哈哈哈");
// 在这里重新进入Runloop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"%@----end----", [NSThread currentThread]);
// NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
// [[NSRunLoop currentRunLoop] run];
/*
it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:.
In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers
*/
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (IBAction)stop {
// 已经停止以后,就不能再调用
if (!self.stopped) {
// 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
}
// 用于停止子线程的RunLoop
- (void)stopThread
{
// 设置标记为NO
self.stopped = YES;
// 停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)dealloc
{
NSLog(@"%s", __func__);
// 如果waitUntilDone为NO,那么会出现坏内存访问,
// 因为这个时候主线程和子线程是同时执行的,子线程在执行代码的时候控制器已经被销毁了
[self stop];
}
@end
复制代码
线程保活封装OC
/** MJThread **/
@interface MJThread : NSThread
@end
@implementation MJThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
/** MJPermenantThread **/
@interface MJPermenantThread()
@property (strong, nonatomic) MJThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end
@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.innerThread = [[MJThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
[self.innerThread start];
}
return self;
}
//- (void)run
//{
// if (!self.innerThread) return;
//
// [self.innerThread start];
//}
- (void)executeTask:(MJPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(MJPermenantThreadTask)task
{
task();
}
@end
复制代码
线程保活封装C语言
/** MJThread **/
@interface MJThread : NSThread
@end
@implementation MJThread
- (void)dealloc
{
NSLog(@"%s", __func__);
}
@end
/** MJPermenantThread **/
@interface MJPermenantThread()
@property (strong, nonatomic) MJThread *innerThread;
@end
@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
if (self = [super init]) {
self.innerThread = [[MJThread alloc] initWithBlock:^{
NSLog(@"begin----");
// 创建上下文(要初始化一下结构体)
CFRunLoopSourceContext context = {0};
// 创建source
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 往Runloop中添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
// 销毁source
CFRelease(source);
// 启动
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
// while (weakSelf && !weakSelf.isStopped) {
// // 第3个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop
// CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
// }
NSLog(@"end----");
}];
[self.innerThread start];
}
return self;
}
- (void)executeTask:(MJPermenantThreadTask)task
{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}
- (void)stop
{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc
{
NSLog(@"%s", __func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop
{
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(MJPermenantThreadTask)task
{
task();
}
@end
复制代码
2.解决NSTimer在滑动时停止工作的问题
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
// NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
// timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
复制代码
3.监控应用卡顿
可以添加 Observer 到主线程 RunLoop 中,通过监听 RunLoop 状态切换的耗时,以达到监控卡顿的目的
// 创建观察者
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
// 添加观察者到runloop的common模式
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);
//创建子线程监控
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//子线程开启一个持续的 loop 用来进行监控
while (YES) {
long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
if (semaphoreWait != 0) {
if (!runLoopObserver) {
timeoutCount = 0;
dispatchSemaphore = 0;
runLoopActivity = 0;
return;
}
//BeforeSources 和 AfterWaiting 这两个状态能够检测到是否卡顿
if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
//将堆栈信息上报服务器的代码放到这里
} //end activity
}// end semaphore wait
timeoutCount = 0;
}// end while
});
复制代码
面试题
-
讲讲 RunLoop,项目中有用到吗?
-
runloop内部实现逻辑?
-
runloop和线程的关系?
线程与runloop是一对一的关系,主线程默认开启runloop,子线程刚创建时并不会开启runloop,而是在第一次获取runloop的时候开启。
-
timer 与 runloop 的关系?
runloop来控制timer什么时候执行。
-
程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
将 timer 添加到 Runloop,并设置为 commonMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 复制代码
-
runloop 是怎么响应用户操作的, 具体流程是什么样的?
- Source1 捕捉用户事件
- 然后交给 Source0 去处理
-
说说runLoop的几种状态
- kCFRunLoopEntry // 即将进入Runloop
- kCFRunLoopBeforeTimers // 即将处理Timer
- kCFRunLoopBeforeSources // 即将处理Source
- kCFRunLoopBeforeWaiting // 即将进入休眠
- kCFRunLoopAfterWaiting // 刚从休眠中唤醒
- kCFRunLoopExit // 即将退出Runloop
-
runloop的mode作用是什么?
隔离不同mode内的资源(source、timer),只需要处理一种模式下的资源,提高流畅度,节省资源。