iOS - RunLoop 基本原理介绍

news/2024/7/20 23:08:26 标签: ios, objective-c, 开发语言

一、Runloop 简介

Runloop 是通过内部维护事件循环来对事件/消息进行管理的一个对象。

事件循环(状态切换)

  • 没有消息需要处理时,休眠以避免资源占用(用户态 -> 内核态)
  • 有消息需要处理时,立刻被唤醒(内核态 -> 用户态)

事件/消息管理:Runloop 通过 mach_msg() 函数接收、发送消息来进行管理。
请添加图片描述

二、Runloop 数据结构

NSRunloop 是 CFRunloop 的封装,提供了面向对象的 API。

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
	// ...
    CFStringRef _name;
    // ...
    CFMutableSetRef _sources0; // <set>
    CFMutableSetRef _sources1; // <set>
    CFMutableArrayRef _observers; // <Array>
    CFMutableArrayRef _times; // <Array>
    // ...
};

// CFRunloop.h 类型重命名
typedef struct __CFRunLoop *CFRunLoopRef;
// CFRunloop.c 结构体
struct __CFRunLoop {
	// ...
    pthread_t _pthread; // runloop 执行线程(runloop 和线程的关系是一一对应)
    // ...
    CFMutableSetRef _commonModes; // <set> String UITrackingRunloopMode / KCFRunloopDefaultMode (一个存储了被标记为 common modes 的模式集合)
    CFMutableSetRef _commonModeItems; // <set> Timer / Observer / Source
    CFRunLoopModeRef _currentMode; // 当前运行的 mode
    CFMutableSetRef _modes; // 内置的 modes
    // ...
};

Runloop 内部存在一个 modes 集合,但 Runloop 只能运行一个 Mode, Runloop 只会处理它当前 Mode 的事件。
请添加图片描述
Runloop 运行模式

  • kCFRunLoopDefaultMode, App的默认运行模式,通常主线程是在这个运行模式下运行
  • UITrackingRunLoopMode, 跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他Mode影响)
  • kCFRunLoopCommonModes, 伪模式,不是一种真正的运行模式
  • UIInitializationRunLoopMode:在刚启动App时第进入的第一个Mode,启动完成后就不再使用
  • GSEventReceiveRunLoopMode:接受系统内部事件,通常用不到

Runloop 中的 commonModes 是一个集合,里面存储着被标记 kCFRunloopCommonModes 的 mode 的 name。

CFRunloopMode 中的 Item
Source0:只包含一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source), 将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop)来唤醒 Runloop,让其处理这个事件。

Source1:基于mach_port,来自系统内核或者其他进程或线程的事件,可以主动唤醒休眠中的 Runloop。

Observer – CFRunloopObserver

  • KCFRunloopEntry (runloop 准备启动)
  • KCFRunloopBeforeTimers (通知观察者,runloop 将要对 Timer 的一些相关事件进行处理了)
  • KCFRunloopBeforeSoureces(将要处理一些 Sources 事件)
  • KCFRunloopBeforeWaiting(即将要发生用户态到内核态的切换)
  • KCFRunloopAfterWaiting(内核态转换为用户态)
  • KCFRunloopExit(runloop 退出通知)

Timer – CFRunloopTimer:是基于时间的触发器,当加入到 Runloop 时,Runloop 会注册对应的时间点,当时间点到时,Runloop 会被唤醒以执行回调。

Runloop 中的 commonModeItems 是当前运行在 commonModes 模式下的 CFRunloopSource / CFRunloopObserver / CFRunloopTimer,其实就是 kCFRunLoopDefaultMode 和 UITrackingRunLoopMode 的 source1 / timter / observers 都包含在 commonModeItems 里面。

当 commonModes 里每多一个 mode, commonModeItems 里就会多一组 source1 / timter / observers

三、Runloop 执行原理

在这里插入图片描述

Runloop 伪代码

SetupThisRunLoopRunTimeoutTimer(); // by GCD timer

//通知即将进入runloop__CFRUNLLOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(KCFRunLoopEntry);
do {

     __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);

     __CFRunLoopDoObservers(kCFRunLoopBeforeSources);

     __CFRunLoopDoBlocks();  //一个循环中会调用两次,确保非延迟的NSObject PerformSelector调用和非延迟的dispatch_after调用在当前runloop执行。还有回调block

     __CFRunLoopDoSource0(); //例如UIKit处理的UIEvent事件

     CheckIfExistMessagesInMainDispatchQueue(); //GCD dispatch main queue

     __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting); //即将进入休眠,会重绘一次界面

     // 休眠,等待事件唤醒
     var wakeUpPort = SleepAndWaitForWakingUpPorts();

     // mach_msg_trap,陷入内核等待匹配的内核mach_msg事件

     // Zzz...

     // Received mach_msg, wake up

     __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);

     // Handle msgs

     if (wakeUpPort == timerPort) {

          __CFRunLoopDoTimers();

     } else if (wakeUpPort == mainDispatchQueuePort) {

          //GCD当调用dispatch_async(dispatch_get_main_queue(),block)时,libDispatch会向主线程的runloop发送mach_msg消息唤醒runloop,并在这里执行。这里仅限于执行dispatch到主线程的任务,dispatch到其他线程的仍然是libDispatch来处理。

          __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()

     } else {

          __CFRunLoopDoSource1();  //CADisplayLink是source1的mach_msg触发?

     }

     __CFRunLoopDoBlocks();

} while (!stop && !timeout);

//通知observers,即将退出runloop
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBERVER_CALLBACK_FUNCTION__(CFRunLoopExit);

3.1 runloop 处理的 6 类事件

1、timer (延迟的 PerformSelector 方法调用,延迟的 dispatch_after,timer事件)
2、observer(runloop中状态变化时进行通知(UI 观察者等))
a. 卡顿检测(1、开辟新线程定时去获取值的状态;2、observer 监听 runloop 的 beforeTimer 开始, beforeWaitingSleep / exit 结束)
3、source0(app 内部事件处理:UIEvent 时间,CFSocket 事件,触摸事件其实是Source1接收系统事件后在回调 __IOHIDEventSystemClientQueueCallback() 内触发的 Source0,Source0 再触发的 _UIApplicationHandleEventQueue()。source0一定是要唤醒runloop及时响应并执行的,如果runloop此时在休眠等待系统的 mach_msg事件,那么就会通过source1来唤醒runloop执行。)
4、source1(处理系统内核的mach_msg事件)
5、Block(PerformSelector 非延迟方法、dispatch_after 立刻调用,block 调用)
6、Main Queue(GCD 中 dispatch 到 main queue 的 block 会被 dispatch 到 main loop执行)

3.2 Runloop mode 切换

上节我们知道,Runloop 每次只能在当前 mode 下执行,那如果需要在不同的 mode 下切换系统是怎么处理的呢?比如界面从静止到滑动时,mode 是如何由 NSRunloopDefaultMode 切换到 UITrackingRunloopMode呢。

CFRunloopRunSpecific 是启动 Runloop 和指定 Runloop 在那个 mode 下执行的 mode, 这个函数一般式操作系统进行 mode 的切换。

CFRunloopRunSpecific 会保持前一次 mode 的状态属性(stopped 和 currentmode)然后发出即将要进入新 mode 通知,然抽进入 __CFRunloopRun(__CFRunloopRun 会创建一个循环),然后这个 mode 运行结束后再发已退出 mode 通知,再恢复前一次的 stopped 和 currentmode。

四、Runloop 实际应用

4.1 AutoreleasePool

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件:

  • BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;
  • Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

4.2 事件响应

苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。

  • 当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。
  • SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
  • _UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。

4.3 手势识别

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

4.4 界面更新

runloop 在 APP 启动的时候会注册一个 UI Observer, UI 变动(frame 变动、UIView/CALayer 层级的变动、 手动设置了 setNeedsLayout/setNeedsDisplay 等)的操作将会提交到全局容器,当 UI Observer 监听到主线程 runloop 进入休眠或者退出的状态,回调去执行一个很长的函数:_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。

4.5 按钮点击

首先是由那个Source1 接收IOHIDEvent,之后在回调 __IOHIDEventSystemClientQueueCallback() 内触发的 Source0,Source0 再触发的 _UIApplicationHandleEventQueue()。所以UIButton事件看到是在 Source0 内的。


http://www.niftyadmin.cn/n/264046.html

相关文章

5G终端异网漫游测试用例介绍

异网漫游新需求,主流手机厂家入网产品都需要尽快支持。 需要SIM卡所在的运营商和漫游运营商有签订过漫游协议,漫游网络允许其接入。 测试用例 终端漫游注册(归属网络无信号) 终端在归属网络无任何蜂窝移动网络信号时,在漫游区域能自动接入拜访的 5G 网络;当终端能搜索…

RRC reconfiguration failure场景介绍

本文介绍UE在ENDC连接状态下,从网络接收到RRC重新配置消息以重新配置SCG(辅助小区组)失败场景。 RRC重新配置消息的IE(信息元素)检查失败,因为网络在发送RRC重新配置消息时没有包含“reestablishRLC”这个IE。如果UE接受了这种没有包含“reestablishRLC”的配置,会导致…

算法记录 | Day42 动态规划

01 背包 0-1 背包问题 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 **0-1 背包问题的特点&#xff1a;**每种物品有且仅有 1 件&…

报错解决:关于swagger的Caused by: java.lang.NullPointerException: null

目录 一、遇到问题 二、解决办法 方法一 方法二 方法二中导入依赖报错的解决方案 一、遇到问题 在往springboot项目里面添加swagger时候&#xff0c;启动的时候就报了如下null了的错误 遇到问题的报错提示&#xff1a; Error starting ApplicationContext. To display the…

性能优化之20个 Linux 服务器性能调优技巧

Linux是一种开源操作系统&#xff0c;它支持各种硬件平台&#xff0c;Linux服务器全球知名&#xff0c;它和Windows之间最主要的差异在于&#xff0c;Linux服务器默认情况下一般不提供GUI(图形用户界面)&#xff0c;而是命令行界面&#xff0c;它的主要目的是高效处理非交互式进…

会声会影导入视频是黑色的 会声会影导入视频只有声音

会声会影是一款功能很成熟的视频编辑软件&#xff0c;其友好的界面设计能照顾到初学者的需求&#xff0c;同时配置的强大功能可满足进阶者的需要。不过由于或硬件或软件的原因&#xff0c;可能会出现会声会影导入视频是黑色的&#xff0c;会声会影导入视频只有声音的问题。本文…

电磁兼容原理、方法及设计的科普好文

什么是电磁兼容 电磁兼容性&#xff08;EMC&#xff09;是指设备或系统在其电磁环境中符合要求运行并不对其环境中的任何设备产生无法忍受的电磁干扰的能力。因此&#xff0c;EMC包括两个方面的要求&#xff1a;一方面是指设备在正常运行过程中对所在环境产生的电磁干扰不能超…

C++设计模式之享元模式

享元模式介绍 享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享对象来减少内存使用和提高性能。在该模式中,我们将需要共享的对象(如字母、数字、图形等)抽象成一个对象接口,然后通过工厂类来维护这些对象的唯一实例,并在需要的时候进行共享,避免了重复创…