一、谈谈对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
启动后立即退出。
网友评论