iOS RunLoop 原理和核心机制

RunLoop的定义

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

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

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

RunLoop特性

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

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<UITouch *> *)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], <MyThread: 0x600002832c00>{number = 8, name = mytestthread}
2022-07-17 11:38:42.158572+0800 DemoTest2022[49641:753494] <TestObject: 0x600003f642a0>
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], <MyThread: 0x600002832c00>{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是维护其内部事件循环的一个对象,它在程序运行过程中重复的做着一些事情,例如接收消息、处理消息、休眠等等。

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

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

猜你喜欢

转载自blog.csdn.net/wywinstonwy/article/details/125654018