美文网首页收藏iOS小筑
iOS开发基础之Runloop

iOS开发基础之Runloop

作者: 华子小筑 | 来源:发表于2015-11-17 14:21 被阅读39次

    文章内容来自:
    深入理解RunLoop
    官方文档-Runloop

    Runloop概念

    • Runloop本身是一个对象(CFRunloopRef或是NSRunloop )
    • 管理事件和消息
    • 提供一种机制:接收到消息或是事件处于运行状态,否则处于休眠避免CPU资源的浪费

    Runloop和线程的关系

    • 线程和Runloop是一一对应的,系统持有一个全局的CFMutableDictionary来保存线程和Runloop;其中key是线程,value对应CFRunloopRef实例;

    • 线程的最初创建如果不获取Runloop是不会自动创建的

    Runloop组成

    Runloop组成
    一个runloop中存在多个Mode,每个Mode中存在Source/Observer/Timer ;每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出runloop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响
    CFRunloopSourceRef (事件产生)
    • source0:包含一个回调的函数指针,它不能主动触发事件,需要调用函数将其标记,然后再唤醒runloop执行;
    • source1:包含一个mach_port和函数指针,被用于处理内核和其他线程发送过来的消息,可以直接唤醒runloop;

    CFRunloopTimerRef

    CFRunloopTimerRef包含一个时间长度和函数指针,runloop会注册时间,当时间一到就会触发回调函数;

    CFRunloopObserverRef

    CFRunloopObserverRef包含一个函数指针,它可以监控runloop的状态,一旦状态改变就会触发回调函数;

     typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
    kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
    };  
    

    Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环

    Runloop的Mode

    CFRunLoopMode 和 CFRunLoop 的结构大致如下:

    struct __CFRunLoopMode {
    CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
    CFMutableSetRef _sources0;    // Set
    CFMutableSetRef _sources1;    // Set
    CFMutableArrayRef _observers; // Array
    CFMutableArrayRef _timers;    // Array
    ...
    };
    
    struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set
    ...
    };   
    

    RunLoop 的内部逻辑

    RunLoop 的内部逻辑

    当以一种Mode启动runloop时,CFRunLoopRunSpecific函数会根据ModeName找到对应的Mode,判断Mode内部是否存在source/observer/timer,如果存在会调用CFRunloopRun函数,CFRunloopRun内部存在一个while循环,runloop会通知observers将要分别启动timer、source0、source1,一旦启动observers中对应的回调函数将会被调用;runloop会通过mach_msg()函数接收系统消息,当这一切执行完毕,当前runloop就会退出!

    AutoreleasePool

    一个runloop循环可以理解创建一个AutoreleasePool,在启动和离开时分别触发AutoreleasePool的push和pop函数,在此次循环结束后完成内存的清理工作;

    事件响应

    • 苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()
    • 触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
    • _UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。

    事件的分发有一个hit-testing的过程,这样能够找到最终处理时间的控件,进而执行自定义的逻辑!

    手势识别

    • 当上面的 _UIApplicationHandleEventQueue() 识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。
    • 苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调
    • 当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

    界面更新

    • 当在操作 UI 时,比如改变了 Frame、更新了 UIView/CALayer 的层次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。
    • 苹果注册了一个 Observer 监听 BeforeWaiting(即将进入休眠) 和 Exit (即将退出Loop) 事件,回调去执行一个很长的函数:
      _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面

    关于网络请求

    • CFSocket 是最底层的接口,只负责 socket 通信。
    • CFNetwork 是基于 CFSocket 等接口的上层封装,ASIHttpRequest 工作于这一层。
    • NSURLConnection 是基于 CFNetwork 的更高层的封装,提供面向对象的接口,AFNetworking 工作于这一层。
    • NSURLSession 是 iOS7 中新增的接口,表面上是和 NSURLConnection 并列的,但底层仍然用到了 NSURLConnection 的部分功能 (比如 com.apple.NSURLConnectionLoader 线程),AFNetworking2 和 Alamofire 工作于这一层。

    与 Runloop 相关的实例

    NSTimer注册的事件不被执行

    日常开发中,与 runLoop 接触得最近可能就是通过 NSTimer 了。一个 Timer 一次只能加入到一个 RunLoop 中。我们日常使用的时候,通常就是加入到当前的 runLoop 的 default mode 中,而 ScrollView 在用户滑动时,主线程 RunLoop 会转到 UITrackingRunLoopMode 。而这个时候, Timer 就不会运行。


    有如下两种解决方案:
    第一种: 设置RunLoop Mode,例如NSTimer,我们指定它运行于 NSRunLoopCommonModes ,这是一个Mode的集合。注册到这个 Mode 下后,无论当前 runLoop 运行哪个 mode ,事件都能得到执行。
    第二种: 另一种解决Timer的方法是,我们在另外一个线程执行和处理 Timer 事件,然后在主线程更新UI。

    相关文章

      网友评论

        本文标题:iOS开发基础之Runloop

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