什么是RunLoop?

RunLoop是通过内部维护的事件循环来对事件和消息进行管理的对象

  • 没有消息需要处理时,Runloop将线程控制器交给系统,即从用户态->内核态,休眠以避免资源占用
  • 有消息需要被处理时,立即唤醒,又从内核态->用户态
    screenshot_2022_12_19_10_44_46.png

Runloop核心

Runloop可以简单地理解成一个while(true)的循环,但又不是while循环这么简单,因为这样的实现会使CPU进行大量无谓的空转。所以,Runloop机制的核心就是保证线程在有events需要处理时能唤醒,在没有events时能进行休眠。
而实现真正的休眠,是靠没有events时从用户态->内核态实现的,当有事件时,系统内核通过mach_msg()或者mach port方法将事件发送给对应的Runloop,Runloop收到事件后从休眠状态切换到唤醒状态,并从内核态->用户态

数据结构

NSRunLoop是CFRunLoop的封装,提供了面向对象的API

  • CFRunLoop
  • CFRunLoopMode
  • Source/Timer/Observer
CFRunLoop{
pthread -----> 线程和runloop是一一对应的
currentMode  ---> CFRunLoopMode
modes  --->  NSMutableSet<CFRunLoopMode*>
commonModes  ---> NSMutableSet<NSString*>
commonModeItems  ---> {多个Observer,Timer, Source}
}
screenshot_2023_02_10_12_21_05.png

CFRunLoopMode

CFRunLoopMode内部数据结构如下

CFRunLoopMode {
name (如NSDefaultRunLoopMode)
sources0  ---> NSMutableSet
sources1  ---> NSMutableSet
observers   ---> NSMutableArray
timers  ---> NSMutableArray
}

mode是管理着Runloop与source/timer/observer之间的桥梁,在一开始会注册五个mode

  • kCFRunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
  • UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。默认NSTimer是被加入到default mode中的,所以当滑动时Runloop切换到tracking mode,这时default mode中的Timer回调不会被调用,所以NSTimer的精度没有CADisplayLinker高。
  • UIInitializationRunLoopMode: 在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用。
  • GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。
  • kCFRunLoopCommonModes/NSRunLoopCommonModes:
    1.这是一个占位的 Mode,不是实际存在的一种mode。
    2.是同步Source/Timer/Observer到多个Mode中的一种技术方案

如果需要将事件加入到多个mode中,则将它注册到commonMode中,该mode实际上是多个mode的集合。
出于将source/timer/observer分隔开的目的,RunLoop一次只能运行在一个mode下,当运行时在RunLoop的currentMode属性中会标记当前运行的mode。而当要切换mode时,RunLoop必须先退出,并选中一个mode重新进入,达到切换mode的目的。在切换mode时,被加入到commonModes中的事件会被拷贝一次到运行的mode中。

CFRunloopObserver

观测时间点

  • kCFRunLoopEntry 将进入runloop
  • kCFRunLoopBeforeTimers
  • kCFRunLoopBeforeSouces
  • kCFRunLoopBeforeWaiting 将要进入休眠状态(即将从用户态切换到内核态
  • kCFRunLoopAfterWaiting 开始唤醒(从内核态进入到用户态不久
    kCFRunLoopExit runloop退出的通知

各个数据结构之间的关系?

RunLoop和线程一一对应--->多个mode--->{多个Source,Timer,Observer}

如何唤醒Runloop

1. CFRunLoopSource

source0: 需要手动唤醒线程

该类Source是App的内部事件,不具有独立唤醒Runloop的能力。一个Source0需要被处理时,他需要被CFRunLoopSourceSignal()函数标记为待处理,并调用CFRunLoopWakeUp函数来唤醒Runloop,CFRunLoopWakeUp函数内部通过一个_wakeUpPort成员变量来唤醒Runloop,推测该变量是一个mach port,Runloop只有通过mach portmach_msg()才可以唤醒。唤醒后通过调用__CFRunLoopDoSources0函数来处理Source0事件,并在之后将该事件标记为已处理。

source1: 具备唤醒线程的能力

一般是由硬件生成的source,如触摸、点击、摇晃、旋转等。此类Source可唤醒Runloop。

2.Timer

使用NSTimer API注册执行的任务,就属于这一类

3. Observer

某个Observer可以监听runloop的状态变化,并作出反应

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 转自bireme,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_阅读 1,475评论 0 5
  • 1 Runloop机制原理 深入理解RunLoop http://www.cocoachina.com/ios/2...
    Kevin_Junbaozi阅读 4,116评论 4 30
  • RunLoop 是 iOS 和 OS X 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,...
    iOS_Alex阅读 919评论 0 10
  • 前言 RunLoop是iOS和OSX开发中非常基础的一个概念,这篇文章将从CFRunLoop的源码入手,介绍Run...
    暮年古稀ZC阅读 2,320评论 1 19
  • 概述 RunLoop作为iOS中一个基础组件和线程有着千丝万缕的关系,同时也是很多常见技术的幕后功臣。尽管在平时多...
    阳明AI阅读 1,119评论 0 17