美文网首页
第八章、RunLoop相关

第八章、RunLoop相关

作者: Evans_Xiao | 来源:发表于2019-08-09 21:36 被阅读0次

    一、谈谈对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创建一个常驻线程

    相关文章

      网友评论

          本文标题:第八章、RunLoop相关

          本文链接:https://www.haomeiwen.com/subject/avpkjctx.html