Runloop

200 阅读7分钟

什么是Runloop

运行循环,不断的跑圈
应用范畴:
定时器(Timer) 1.PerformSelector
2.GCD Async Main Queue
3.事件响应、手势识别、界面刷新
4.网络请求
5.AutoreleasePool

如果没有运行循环,程序运行结束就会退出
如果有了Runloop,程序不会马上退出,而是继续保持运行状态。 其实内部就是个do-while循环

基本作用:

  1. 保持程序的持续运行
  2. 处理App中的各种事件(比如触摸事件、定时器事件等)
  3. 节省CPU资源,提高程序性能:该做事时做事,该休息时休息

Runloop对象

iOS中有2套API来访问和使用RunLoop Foundation框架:NSRunLoop [NSRunLoop currentRunLoop] (其实就是封装的CFRunLoopRef)
Core Foundation框架:CFRunLoopRef

Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

Runloop与线程

  1. 每条线程都有唯一的一个与之对应的RunLoop对象

  2. RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value

  3. 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建

  4. RunLoop会在线程结束时销毁

  5. 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop

Runloop相关的类

Core Foundation中关于RunLoop的5个类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef

CFRunLoopModeRef

CFRunLoopModeRef结构:

  1. CFRunLoopModeRef代表RunLoop的运行模式

  2. 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer

  3. RunLoop启动时只能选择其中一个Mode,作为currentMode

  4. 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入 不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响

  5. 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出

常见Mode

  1. kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行

  2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响

CFRunLoopObserverRef

添加Observer监听RunLoop的所有状态

RunLoop的运行逻辑

01、通知Observers:进入Loop
02、通知Observers:即将处理Timers
03、通知Observers:即将处理Sources
04、处理Blocks
05、处理Source0(可能会再次处理Blocks)
06、如果存在Source1,就跳转到第8步
07、通知Observers:开始休眠(等待消息唤醒)
08、通知Observers:结束休眠(被某个消息唤醒)
8.01> 处理Timer
802> 处理GCD Async To Main Queue
803> 处理Source1
09、处理Blocks
10、根据前面的执行结果,决定如何操作
10.01> 回到第02步
10.02> 退出Loop
11、通知Observers:退出Loop

Source0
触摸事件处理 performSelector:onThread:

Source1
基于Port的线程间通信
系统事件捕捉(比如点击事件,先是包装成source1,然后分发到source0来进行处理)

Timers
NSTimer
performSelector:withObject:afterDelay:

Observers
用于监听RunLoop的状态 UI刷新(BeforeWaiting) Autorelease pool(BeforeWaiting)

内部代码:

/// 用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);
    
    /// 内部函数,进入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;
            }
            
            /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
            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休眠的实现逻辑

mach_msg() 函数实际上是调用了一个 Mach 陷阱 (trap),即函数mach_msg_trap(),陷阱这个概念在 Mach 中等同于系统调用。当你在用户态调用 mach_msg_trap() 时会触发陷阱机制,切换到内核态;内核态中内核实现的 mach_msg() 函数会完成实际的工作

Runloop应用

控制线程的生命周期(线程保活)

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
    [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(@"真正要做的事情");
}

// 此方法的目的就是为了方法保活
- (void)run
{
    //1. 无效 mode中无任何source、time,oberserve等
//    [[NSRunLoop currentRunLoop] run];
    
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}

以上代码可以实现在VC中实现线程保活,但是控制器销毁的时候控制器和线程都没有释放,我们做以下尝试

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.thread = [[NSThread alloc]initWithBlock:^{
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    }];
    [self.thread start];
}

run中代码移至线程创建的Block中,最终VC释放了,但是线程还是没有释放,根本原因在于RunLoop还在启动中,我们剩下要做的就是停掉RunLoop,但是一定要注意:[[NSRunLoop currentRunLoop] run]run方法是永远无法停止的,即使我们做了如下操作

-(void)dealloc
{
    [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:NO];
    
}

- (void)stop
{
    CFRunLoopStop(CFRunLoopGetCurrent());
}

依然无法停止运行循环,上面说了调用run方法之后,Runloop永无止境
最终方案:

- (void)viewDidLoad
{
    [super viewDidLoad];
    _isStop = NO;
    __weak typeof(self) weakSelf = self;
    self.thread = [[NSThread alloc]initWithBlock:^{
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];
        🌹// 防止当self销毁之后,走到这导致weakSelf.isStop = NO的问题,这里写成weakSelf && !weakSelf.isStop可以完美解决问题
        while (weakSelf && !weakSelf.isStop) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }];
    [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(@"真正要做的事情");
}

// 此方法的目的就是为了方法保活
- (void)run
{
    //1. 无效 mode中无任何source、time,oberserve等
//    [[NSRunLoop currentRunLoop] run];
    
    
}

-(void)dealloc
{
    🌹//waitUntilDone 是否等待线程任务执行完成再执行后面的代码,这里如果是NO的话,会继续向下执行,会销毁self当前控制器,而stop方法里面还用到self,造成坏内存访问
    [self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
    self.thread = nil;
}

- (void)stop
{
    self.isStop = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
}

解决NSTimer在滑动时候停止工作的问题

scheduledTimerWithTimeInterval默认是将定时器加到Runloop的defaultModel中,将其加入CommonMode即可

为什么NSTimer不准?

监控应用卡顿

性能优化

相关博客

深入理解RunLoop

深入RunLoop

iOS概念攻坚之路(一):RunLoop