第八章、RunLoop相关

2019-08-09  本文已影响0人  Evans_Xiao

一、谈谈对RunLoop的使用理解

保持程序持续运行,程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoopRunLoop保证主线程不会被销毁,也就保证了程序的持续运行。

UIApplicationMain函数内启动了RunLoop,程序不会马上退出,而是保持运行状态。故每一个应用必须要有一个RunLoop。我们知道主线程一开起来,就会跑一个和主线程对应的RunLoop,那么RunLoop一定是在程序的入口main函数中开启。

二、RunLoop内部实现逻辑?

RunLoop的源码

// 用DefaultMode启动
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

这里发现RunLoop确实是do while通过判断result的值实现的。因此,可以把RunLoop看成一个死循环。如果没有RunLoopUIApplicationMain函数执行完毕之后将直接返回,也就没有程序持续运行一说了。

因为Fundation框架是基于CFRunLoopRef的一层OC封装,所以可以主要研究CFRunLoopRef源码对RunLoop进行更深一层分析理解。

三、谈谈RunLoop和线程的关系

(1)每条线程都有唯一的一个与之对应的RunLoop对象
(2)RunLoop保存在一个全局的Dictionary里,线程作为keyRunLoop作为value
(3)主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
(4)RunLoop在第一次获取时创建,在线程结束时销毁

四、谈谈NSTimer与RunLoop的关系

NSTimer只是被加到了kCFRunLoopDefaultMode模式下,当scroll被滑动时,RunLoop被切换到了UITrackingRunLoopMode模式下,所以NSTimer自然就不工作了。

简单理解,同一时刻RunLoop只能在一种模式下运行,处理一种模式下的状态。

更多参考Timer与RunLoop

五、程序中添加每3秒响应一次的NSTimer,当拖动tableview时NSTimer可能无法响应要怎么解决?

NSTimer *timer = [NSTimer timerWithTimeInterval:2 repeats:YES block:^(NSTimer * _Nonnull timer) {  
        NSLog(@"test");  
    }];  
    /*
    FOUNDATION_EXPORT NSRunLoopMode const NSDefaultRunLoopMode;
    FOUNDATION_EXPORT NSRunLoopMode const NSRunLoopCommonModes  API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    */
[[NSRunLoop currentRunLoop] addTimer:timer1 forMode:NSRunLoopCommonModes];

原因分析:
如果当前线程就是主线程,也就是UI线程时,某些UI事件,比如UIScrollView的拖动操作,会将RunLoop切换成NSEventTrackingRunLoopMode模式,在这个过程中,默认的NSDefaultRunLoopMode模式中注册的事件是不会被执行的。也就是说,此时使用scheduledTimerWithTimeInterval添加到RunLoop中的NSTimer就不会执行。

解决原因:
为了设置一个不被UI干扰的NSTimer,我们需要手动创建一个NSTimer,然后使用NSRunLoop的addTimer:forMode:方法来把NSTimer按照指定模式加入到RunLoop中。这里使用的模式是:NSRunLoopCommonModes,这个模式等效于NSDefaultRunLoopModeNSEventTrackingRunLoopMode的结合。

六、RunLoop是怎么响应用户操作的,谈谈具体流程

按键(HOME键、锁屏键、音量键等)、传感器(摇晃、加速等)、触摸屏幕等【物理事件】会触发IOKit.framework生成一个IOHIDEvent对象,然后SpringBoard会接收这个对象并通过mach port发给当前App的进程;接下来进程会触发RunLoop的基于port的Source1回调一个__IOHIDEventSystemClientQueueCallback()的API,这个API会相应触发Source0来调用__UIApplicationHandleEventQueue(),而此API再将传递到此的IOHIDEvent处理包装成上层所熟悉的UIEvent。最后UIEvent会被分发给UIWindow根据Respond chain来响应事件。

梳理整个流程如下:

物理事件 (按键、传感器、触摸等)
|
IOHIDEvent (由IOKit.framework生成,SpringBoard接收)
|
App进程 (由mach port内核消息通信机制传递Event,SpringBoard->App)
|
触发Source1
|
回调 __IOHIDEventSystemClientQueueCallback()
|
触发Source0
|
回调__UIApplicationHandleEventQueue()
|
将IOHIDEvent封装成UIEvent
|
识别此事件是UIGesture或屏幕旋转等
|
分发UIWindow
|
根据响应链交给对应的responder进行事件回调

而这整个事件处理流程是基于RunLoop的基本处理循环进行的。在main函数开始后,主线程的RunLoop对象被创建完。如UIEvent、UI绘制等会统一在主线程的RunLoop对象的即将进入休眠前的时间点触发各自对应的代理回调方法,然后RunLoop进入休眠,直到被NSTimer定时器或Source1发来的内核消息事件唤醒,再分别对Timer、Source0、Source1发来的事件进行处理回调。

七、谈谈对RunLoop的几种状态的理解

目前已知的Mode有5种:
1、kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2、UITrackingRunLoopMode:界面跟踪Mode,用于UIScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
3、UIInitializationRunLoopMode:在刚启动App时进入的第一个Mode,启动完成后就不再使用
4、GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
5、kCFRunLoopCommonModes:一个占位用的Mode,不是一种真正的Mode

八、说一说RunLoop的mode作用

1、model主要是用来指定事件在运行循环中的优先级的,分为:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode):默认,空闲状态
UITrackingRunLoopMode:UIScrollView滑动时
UIInitializationRunLoopMode:启动时
NSRunLoopCommonModes(kCFRunLoopCommonModes):Mode集合

2、苹果公开提供的Mode有两个:
NSDefaultRunLoopMode(kCFRunLoopDefaultMode)
NSRunLoopCommonModes(kCFRunLoopCommonModes)

九、实现一个常驻线程

1、为当前线程开启一个RunLoop
2、向该RunLoop中添加一个Port/Source等维持RunLoop的事件循环
3、启动该RunLoop

简单理解,只要往RunLoop中添加了timer、source或者observer就会继续执行,一个RunLoop通常必须包含一个输入源或者定时器来监听事件,如果一个都没有,RunLoop启动后立即退出。

更多参考iOS利用RunLoop创建一个常驻线程

上一篇下一篇

猜你喜欢

热点阅读