iOS-RunLoop

RunLoop概念:

        运行循环。实际上是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件(比如说触摸事件、UI刷新事件、定时器事件、Selector事件),从而保持程序的持续运行。

        在没有事件处理时,会进入睡眠状态,节省CPU资源,直到有事件将它唤醒。

RunLoop与线程的关系:

      RunLoop与线程是一一对应的,每一个线程都有唯一一个对应的RunLoop。即使在当前线程中开辟出子线程,也会有与子线程对应的RunLoop。但是在线程刚创建的时候并没有RunLoop,RunLoop的创建发生在第一次获取时[NSRunLoop currentRunLoop],销毁在线程结束时。(主线程的RunLoop是开启的)

       线程(创建)-->runloop将进入-->最高优先级OB创建释放池-->runloop将睡-->最低优先级OB销毁旧池创建新池-->runloop将退出-->最低优先级OB销毁新池-->线程(销毁)

      RunLoop 并不保证线程安全。我们只能在当前线程内部操作当前线程的 RunLoop 对象,而不能在当前线程内部去操作其他线程的 RunLoop 对象方法。

【自动释放池】自动释放池寄生于Runloop:程序启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler(),一是,监测Entry(即将进入Loop)状态,其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池,其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前;另外一个,Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
RunLoop数据结构:

runloop相关的数据结构:

  • CFRunLoop
    • 
      struct __CFRunLoop {
          CFMutableSetRef _commonModes;     // Set
          CFMutableSetRef _commonModeItems; // Set
          CFRunLoopModeRef _currentMode;    // Current Runloop Mode
          CFMutableSetRef _modes;           // Set
          ...
      };
  • CFRunLoopMode
  • struct __CFRunLoopMode {
        CFStringRef _name;            // Mode名字,默认是NSDefaultRunLoopMode 
        CFMutableSetRef _sources0;    // Set
        CFMutableSetRef _sources1;    // Set
        CFMutableArrayRef _observers; // Array
        CFMutableArrayRef _timers;    // Array
        ...
    };

    Source:

  • source0: event事件,只含有回调,需要标记待处理(signal),然后手动将runloop唤醒(wakeup)

        source0 :执行performSelectors方法,假如你在主线程performSelectors一个任务到子线程,这时候就是在代码中发送事件到子线程的runloop,这时候如果子线程开启了runloop就会执行该任务,注意该performSelector方法只有在你子线程开启runloop才能执行,如果你没有在子线程中开启runloop,那么该操作会无法执行

  • source1:包含一个 mach_port 和一个回调,被用于通过内核和其他线程发送的消息,能主动唤醒runloop。
  • source1 :苹果创建用来接受系统发出事件,当手机发生一个触摸,摇晃或锁屏等系统,这时候系统会发送一个事件到app进程(进程通信),这也就是为什么叫基于port传递source1的原因,port就是进程端口,该事件可以激活进程里线程的runloop,比如你点击一下app的按钮或屏幕,runloop就醒过来处理触摸事件,处理结束后进入休眠。

        Timer

  • 基于事件的定时器
  • 与NSTimer 免费桥接转换

         Observer

         观测时间点,共有六种状态

  • kCFRunLoopEntry:即将进入Loop
  • kCFRunLoopBeforeTimers:  即将处理 Timer
  • kCFRunLoopBeforeSources: 即将处理 Source
  • kCFRunLoopBeforeWaiting: 即将进入休眠
  • kCFRunLoopAfterWaiting: 刚从休眠中唤醒
  • kCFRunLoopExit: 即将退出Loop
  •  

iOS-RunLoop_第1张图片

 

RunLoop、Model、Source/Timer/Observer关系
RunLoop和Model是一对多的关系(从CFRunLoop的数据结构modes)。
Model和Source/Timer/Observer是一对多的关系。

Model类型:

  • kCFRunLoopDefaultMode: 默认 mode,通常主线程在这个 Mode 下运行。
  • UITrackingRunLoopMode: 追踪mode,保证Scrollview滑动顺畅不受其他 mode 影响。
  • UIInitializationRunLoopMode: 启动程序后的过渡mode,启动完成后就不再使用。
  • GSEventReceiveRunLoopMode: Graphic相关事件的mode,通常用不到。
  • NSRunLoopCommonModes: 占位mode,作为标记DefaultMode和CommonMode用。CommonMode不是实际存在的一种Mode。是同步Source/Timer/Observer到多个Model中的一种技术方案。
  • 对于最后一个commonModes:

  • 一个 Mode 可以将自己标记为"Common"属性(通过将其 ModeName 添加到 RunLoop 的 "commonModes" 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 标记的所有Mode里。

    应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为"Common"属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。

    有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到commonMode 中。那么所有被标记为commonMode的mode(defaultMode和TrackingMode)都会执行该timer。这样你在滑动界面的时候也能够调用timer。

    多个model能够起到屏蔽事件的作用。

循环机制:

  • iOS-RunLoop_第2张图片

 

在每次运行开启RunLoop的时候,所在线程的RunLoop会自动处理之前未处理的事件,并且通知相关的观察者。

具体的顺序如下:

  1. 通知观察者RunLoop已经启动
  2. 通知观察者即将要开始的定时器
  3. 通知观察者任何即将启动的非基于端口的源
  4. 启动任何准备好的非基于端口的源
  5. 如果基于端口的源准备好并处于等待状态,立即启动;并进入步骤9
  6. 通知观察者线程进入休眠状态
  7. 将线程置于休眠知道任一下面的事件发生:
    • 某一事件到达基于端口的源
    • 定时器启动
    • RunLoop设置的时间已经超时
    • RunLoop被显示唤醒
  8. 通知观察者线程将被唤醒
  9. 处理未处理的事件
    • 如果用户定义的定时器启动,处理定时器事件并重启RunLoop。进入步骤2
    • 如果输入源启动,传递相应的消息
    • 如果RunLoop被显示唤醒而且时间还没超时,重启RunLoop。进入步骤2
  10. 通知观察者RunLoop结束。

待补充。。。

参考文章:https://blog.csdn.net/hherima/article/details/51746125

你可能感兴趣的:(iOS开发)