一、谈谈对RunLoop的使用理解
保持程序持续运行,程序一启动就会开一个主线程,主线程一开起来就会跑一个主线程对应的RunLoop,RunLoop保证主线程不会被销毁,也就保证了程序的持续运行。
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看成一个死循环。如果没有RunLoop,UIApplicationMain函数执行完毕之后将直接返回,也就没有程序持续运行一说了。
因为Fundation框架是基于CFRunLoopRef的一层OC封装,所以可以主要研究CFRunLoopRef源码对RunLoop进行更深一层分析理解。
三、谈谈RunLoop和线程的关系
(1)每条线程都有唯一的一个与之对应的RunLoop对象
(2)RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为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,这个模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的结合。
六、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启动后立即退出。












网友评论