RunLoop的定义

当有持续的异步任务需求时,我们会创建一个独立的生命周期可控的线程。RunLoop就是控制线程生命周期并接收事件进行处理的机制。

RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。

Foundation: NSRunLoop Core Foundation: CFRunLoop 核心部分,代码开源,C 语言编写,跨平台

RunLoop特性

主线程的RunLoop在应用启动的时候就会自动创建其他线程则需要在该线程下自己启动不能自己创建RunLoopRunLoop并不是线程安全的,所以需要避免在其他线程上调用当前线程的RunLoopRunLoop负责管理autorelease poolsRunLoop负责处理消息事件,即输入源事件和计时器事件

RunLoop数据结构

runloop数据结构

//线程-》runloop-》内存 dict

//pthread getter runloop

//runloop:items->mode->run->thread

struct __CFRunLoop {

CFRuntimeBase _base;

pthread_mutex_t _lock; /* locked for accessing mode list */

__CFPort _wakeUpPort; // used for CFRunLoopWakeUp

Boolean _unused;

volatile _per_run_data *_perRunData; // reset for runs of the run loop

pthread_t _pthread;//对应线程

uint32_t _winthread;

CFMutableSetRef _commonModes;//模型集合

CFMutableSetRef _commonModeItems;//任务

CFRunLoopModeRef _currentMode;//当前mode,可以切换,timer UI

CFMutableSetRef _modes;//

struct _block_item *_blocks_head;

struct _block_item *_blocks_tail;

CFAbsoluteTime _runTime;

CFAbsoluteTime _sleepTime;

CFTypeRef _counterpart;

};

Runloop与线程,Mode对应关系

 

 

NSRunLoop(Foundation)是CFRunLoop(CoreFoundation)的封装,提供了面向对象的API RunLoop 相关的主要涉及五个类:

CFRunLoop:RunLoop对象CFRunLoopMode:运行模式CFRunLoopSource:输入源/事件源CFRunLoopTimer:定时源CFRunLoopObserver:观察者

1、CFRunLoop

由pthread(线程对象,说明RunLoop和线程是一一对应的)、currentMode(当前所处的运行模式)、modes(多个运行模式的集合)、commonModes(模式名称字符串集合)、commonModelItems(Observer,Timer,Source集合)构成

2、CFRunLoopMode

由name、source0、source1、observers、timers构成

3、分为source0和source1两种

source0: 即非基于port的,也就是用户触发的事件。需要手动唤醒线程,将当前线程从内核态切换到用户态 source1:基于port的,包含一个 mach_port 和一个回调,可监听系统端口和通过内核和其他线程发送的消息,能主动唤醒RunLoop,接收分发系统事件,具备唤醒线程的能力。

4、CFRunLoopTimer

于时间的触发器,基本上说的就是NSTimer。在预设的时间点唤醒RunLoop执行回调。因为它是基于RunLoop的,因此它不是实时的(就是NSTimer 是不准确的。 因为RunLoop只负责分发源的消息。如果线程当前正在处理繁重的任务,就有可能导致Timer本次延时,或者少执行一次)

5、CFRunLoopObserver

监听以下时间点:CFRunLoopActivity

RunLoop机制 

主线程 (有 RunLoop 的线程) 几乎所有函数都从以下六个之一的函数调起:

CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION CFRunloop is calling out to an abserver callback function 用于向外部报告 RunLoop 当前状态的更改,框架中很多机制都由 RunLoopObserver 触发,如 CAAnimation CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK CFRunloop is calling out to a block 消息通知、非延迟的perform、dispatch调用、block回调、KVO CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE CFRunloop is servicing the main desipatch queue CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION CFRunloop is calling out to a timer callback function 延迟的perform, 延迟dispatch调用 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION CFRunloop is calling out to a source 0 perform function 处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket。普通函数调用,系统调用 CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION CFRunloop is calling out to a source 1 perform function 由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort

NSTimer,延迟的perform, 延迟dispatch调用

处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket。普通函数调用,系统调用

RunLoop 架构

 runloop运行时

主要有以下六种状态:

kCFRunLoopEntry -- 进入runloop循环kCFRunLoopBeforeTimers -- 处理定时调用前回调kCFRunLoopBeforeSources -- 处理input sources的事件kCFRunLoopBeforeWaiting -- runloop睡眠前调用kCFRunLoopAfterWaiting -- runloop唤醒后调用kCFRunLoopExit -- 退出runloop

RunLoop的内部逻辑

 

//用DefaultMode启动

void CFRunLoopRun(void) {

CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);

}

//用指定的Mode启动,允许设置RunLoop的超时时间

int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {

return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);

}

//RunLoop的实现

int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {

//首先根据modeName找到对应的mode

CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);

//如果mode中没有source/timer/observer,直接返回

if (__CFRunLoopModeIsEmpty(currentMode)) return;

//1.通知Observers:RunLoop即将进入loop

__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);

//调用函数__CFRunLoopRun 进入loop

__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {

Boolean sourceHandledThisLoop = NO;

int retVal = 0;

do {

//2.通知Observers:RunLoop即将触发Timer回调

__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);

//3.通知Observers:RunLoop即将触发Source0(非port)回调

__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);

///执行被加入的block

__CFRunLoopDoBlocks(runloop, currentMode);

//4.RunLoop触发Source0(非port)回调

sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);

//执行被加入的Block

__CFRunLoopDoBlocks(runloop, currentMode);

//5.如果有Source1(基于port)处于ready状态,直接处理这个Source1然后跳转去处理消息

if (__Source0DidDispatchPortLastTime) {

Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)

if (hasMsg) goto handle_msg;

}

//6.通知Observers:RunLoop的线程即将进入休眠

if (!sourceHandledThisLoop) {

__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);

}

//7.调用mach_msg等待接收mach_port的消息。线程将进入休眠,直到被下面某个事件唤醒

// 一个基于port的Source的事件

//一个Timer时间到了

//RunLoop自身的超时时间到了

//被其他什么调用者手动唤醒

__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {

mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg

}

//8.通知Observers:RunLoop的线程刚刚被唤醒

__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

//收到消息,处理消息

handle_msg:

//9.1 如果一个Timer时间到了,触发这个timer的回调

if (msg_is_timer) {

__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())

}

//9.2 如果有dispatch到main_queue的block,执行block

else if (msg_is_dispatch) {

__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);

}

//9.3 如果一个Source1(基于port)发出事件了,处理这个事件

else {

CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);

sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);

if (sourceHandledThisLoop) {

mach_msg(reply, MACH_SEND_MSG, reply);

}

}

//执行加入到loop的block

__CFRunLoopDoBlocks(runloop, currentMode);

if (sourceHandledThisLoop && stopAfterHandle) {

/// 进入loop时参数说处理完事件就返回。

retVal = kCFRunLoopRunHandledSource;

} else if (timeout) {

/// 超出传入参数标记的超时时间了

retVal = kCFRunLoopRunTimedOut;

} else if (__CFRunLoopIsStopped(runloop)) {

/// 被外部调用者强制停止了

retVal = kCFRunLoopRunStopped;

} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {

/// source/timer/observer一个都没有了

retVal = kCFRunLoopRunFinished;

}

// 如果没超时,mode里没空,loop也没被停止,那继续loop。

} while (retVal == 0);

}

//10. 通知Observers:RunLoop即将退出

__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

}

RunLoop在实际开发中的应用 

控制线程生命周期(线程保活)解决NSTimer在滑动时停止工作的问题监控应用卡顿性能优化

1、线程保活

平时创建子线程时,线程上的任务执行完这个线程就会销毁掉。 有时我们会需要经常在一个子线程中执行任务,频繁的创建和销毁线程就会造成很多的开销,这时我们可以通过runloop来控制线程的生命周期

RunLoop启动方法

三种启动RunLoop的方法

run,无条件runUntilDate, 设置时间限制runMode:before:Date:,在特定模式下

self.stopped = NO;

__weak typeof(self) weakSelf = self;

self.thread = [[MyThread alloc] initWithBlock:^{

NSLog(@"新线程");

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

switch (activity) {

case kCFRunLoopEntry:

NSLog(@"即将进入RunLoop");

break;

case kCFRunLoopBeforeTimers:

NSLog(@"即将处理Timer");

break;

case kCFRunLoopBeforeSources:

NSLog(@"即将处理Sources");

break;

case kCFRunLoopBeforeWaiting:

NSLog(@"即将进入休眠");

break;

case kCFRunLoopAfterWaiting:

NSLog(@"从休眠中唤醒");

break;

case kCFRunLoopExit:

NSLog(@"即将退出RunLoop");

break;

default:

break;

}

});

// 监听RunLoop的状态变化

CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];

while (!weakSelf.stopped) {

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

NSLog(@"-----------while---");

}

NSLog(@"end");

}];

self.thread.name=@"mytestthread";

[self.thread start];

常驻线程停止的方式

- (void)pressStop {

//子线程中调用stop

if (_stopped == NO ) {

[self performSelector:@selector(stop) onThread:_thread withObject:nil waitUntilDone:YES];

}

}

//停止子线程的runloop

- (void)stop {

//设置标记yes

self.stopped = YES;

//停止runloop

CFRunLoopStop(CFRunLoopGetCurrent());

NSLog(@"%s, %@", __func__, [NSThread currentThread]);

//解除引用, 停止runloop这个子线程就会dealloc

self.thread = nil;

}

2、NSTimer相关问题

创建timer使用了带有scheduledTimer的方法,创建的timer是在runloop默认模式下,也就是NSDefaultRunLoopMode。 当拖动模拟机上的scrollView时,定时器就会失效,停止拖动,定时器恢复。说明定时器并不在UITrackingRunLoopMode模式(mode)下。只需要将这个timer也添加到UITrackingRunLoopMode模式下就可以正常工作

[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];

或者

[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];

[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:UITrackingRunLoopMode];

 3、监控应用卡顿

iOS之卡顿检测_风雨「83」的博客-CSDN博客_ios 卡顿检测

//

// RunloopViewController.m

// DemoTest2022

//

// Created by wangyun on 2022/6/21.

//

#import "RunloopViewController.h"

#import "MyThread.h"

#import "TestObject.h"

@interface RunloopViewController ()

@property(nonatomic,strong) MyThread *thread;

@property (nonatomic, assign) BOOL stopped;

@end

@implementation RunloopViewController

- (void)viewDidLoad {

[super viewDidLoad];

self.view.backgroundColor = [UIColor greenColor];

UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];

[self.view addSubview:button];

[button addTarget:self action:@selector(pressPrint) forControlEvents:UIControlEventTouchUpInside];

[button setTitle:@"执行任务" forState:UIControlStateNormal];

button.frame = CGRectMake(100, 200, 100, 20);

UIButton *stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];

[self.view addSubview:stopButton];

[stopButton addTarget:self action:@selector(pressStop) forControlEvents:UIControlEventTouchUpInside];

[stopButton setTitle:@"停止RunLoop" forState:UIControlStateNormal];

stopButton.frame = CGRectMake(100, 400, 100, 20);

self.stopped = NO;

__weak typeof(self) weakSelf = self;

self.thread = [[MyThread alloc] initWithBlock:^{

NSLog(@"新线程");

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {

switch (activity) {

case kCFRunLoopEntry:

NSLog(@"即将进入RunLoop");

break;

case kCFRunLoopBeforeTimers:

NSLog(@"即将处理Timer");

break;

case kCFRunLoopBeforeSources:

NSLog(@"即将处理Sources");

break;

case kCFRunLoopBeforeWaiting:

NSLog(@"即将进入休眠");

break;

case kCFRunLoopAfterWaiting:

NSLog(@"从休眠中唤醒");

break;

case kCFRunLoopExit:

NSLog(@"即将退出RunLoop");

break;

default:

break;

}

});

// 监听RunLoop的状态变化

CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];

while (!weakSelf.stopped) {

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

NSLog(@"-----------while---");

}

NSLog(@"end");

}];

self.thread.name=@"mytestthread";

[self.thread start];

}

- (void)pressPrint {

//子线程中调用print

[self performSelector:@selector(print) onThread:_thread withObject:nil waitUntilDone:NO];

}

//子线程需要执行的任务

- (void)print {

NSLog(@"%s, %@", __func__, [NSThread currentThread]);

__autoreleasing TestObject *model = [[TestObject alloc] init];

NSLog(@"%@",model);

}

- (void)pressStop {

//子线程中调用stop

if (_stopped == NO ) {

[self performSelector:@selector(stop) onThread:_thread withObject:nil waitUntilDone:YES];

}

}

//停止子线程的runloop

- (void)stop {

//设置标记yes

self.stopped = YES;

//停止runloop

CFRunLoopStop(CFRunLoopGetCurrent());

NSLog(@"%s, %@", __func__, [NSThread currentThread]);

//解除引用, 停止runloop这个子线程就会dealloc

self.thread = nil;

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

//退出当前页面

//保证这个vc销毁时,子线程也要销毁

[self pressStop];

[self.navigationController popViewControllerAnimated:YES];

}

- (void)dealloc {

NSLog(@"%s", __func__);

}

/*

#pragma mark - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

// Get the new view controller using [segue destinationViewController].

// Pass the selected object to the new view controller.

}

*/

@end

 进入vc log:

2022-07-17 11:37:43.352422+0800 DemoTest2022[49641:753494] 新线程 2022-07-17 11:37:43.352952+0800 DemoTest2022[49641:753494] 即将进入RunLoop 2022-07-17 11:37:43.353264+0800 DemoTest2022[49641:753494] 即将处理Timer 2022-07-17 11:37:43.353542+0800 DemoTest2022[49641:753494] 即将处理Sources 2022-07-17 11:37:43.353801+0800 DemoTest2022[49641:753494] 即将进入休眠

点击执行任务 log:

2022-07-17 11:38:42.157601+0800 DemoTest2022[49641:753494] 从休眠中唤醒 2022-07-17 11:38:42.157805+0800 DemoTest2022[49641:753494] 即将处理Timer 2022-07-17 11:38:42.158081+0800 DemoTest2022[49641:753494] 即将处理Sources 2022-07-17 11:38:42.158376+0800 DemoTest2022[49641:753494] -[RunloopViewController print], {number = 8, name = mytestthread} 2022-07-17 11:38:42.158572+0800 DemoTest2022[49641:753494] 2022-07-17 11:38:42.158790+0800 DemoTest2022[49641:753494] -[TestObject dealloc] 2022-07-17 11:38:42.159116+0800 DemoTest2022[49641:753494] 即将退出RunLoop 2022-07-17 11:38:42.159287+0800 DemoTest2022[49641:753494] -----------while--- 2022-07-17 11:38:42.159494+0800 DemoTest2022[49641:753494] 即将进入RunLoop 2022-07-17 11:38:42.159817+0800 DemoTest2022[49641:753494] 即将处理Timer 2022-07-17 11:38:42.160106+0800 DemoTest2022[49641:753494] 即将处理Sources 2022-07-17 11:38:42.160390+0800 DemoTest2022[49641:753494] 即将进入休眠

点击屏幕log:

2022-07-17 11:39:53.883534+0800 DemoTest2022[49641:753494] 从休眠中唤醒 2022-07-17 11:39:53.883898+0800 DemoTest2022[49641:753494] 即将处理Timer 2022-07-17 11:39:53.884167+0800 DemoTest2022[49641:753494] 即将处理Sources 2022-07-17 11:39:53.884552+0800 DemoTest2022[49641:753494] -[RunloopViewController stop], {number = 8, name = mytestthread} 2022-07-17 11:39:53.884873+0800 DemoTest2022[49641:753494] 即将退出RunLoop 2022-07-17 11:39:53.885192+0800 DemoTest2022[49641:753494] -----------while--- 2022-07-17 11:39:53.885500+0800 DemoTest2022[49641:753494] end 2022-07-17 11:39:53.886056+0800 DemoTest2022[49641:753494] -[MyThread dealloc] 2022-07-17 11:39:54.399634+0800 DemoTest2022[49641:753178] -[RunloopViewController dealloc]

总结

RunLoop是维护其内部事件循环的一个对象,它在程序运行过程中重复的做着一些事情,例如接收消息、处理消息、休眠等等。

所谓的事件循环,就是对事件/消息进行管理,没有消息时,休眠线程以避免资源消耗,从用户态切换到内核态。

有事件/消息需要进行处理时,立即唤醒线程,回到用户态进行处理。  

相关链接

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。