基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

RunLoop 的基本作用和模式 (Mode) 有哪些?

知识点图片

RunLoop 是 iOS 和 macOS 开发中非常核心的基础组件,它本质上是一个处理事件的循环(Event Loop)。

简单来说,RunLoop 的目的就是:让线程在有工作的时候忙碌,没有工作的时候休眠(省电/省资源)。

以下是关于 RunLoop 的基本作用和模式(Mode)的详细解析:


一、 RunLoop 的基本作用

RunLoop 主要有以下四个核心作用:

  1. 保持线程存活 (Keep Alive)

    • 如果没有 RunLoop,线程执行完入口函数(如 main 函数或线程的 block)后就会立即退出。
    • RunLoop 启动后,会进入一个 do-while 循环,使线程一直处于“运行-休眠-运行”的状态,从而保证 App 不会一启动就退出(主线程),或者后台线程能持续等待任务。
  2. 处理各种事件 (Event Processing)

    • RunLoop 负责监听和分发各种事件,包括:
      • 用户交互事件:点击、滑动、屏幕刷新(CADisplayLink)。
      • 系统事件:定时器(NSTimer)、网络请求回调、GCD 的 Main Queue 分发。
      • Selector 调用performSelector... 系列方法。
  3. 节省 CPU 资源 (Save Resources)

    • 这是 RunLoop 的精髓。当没有事件处理时,RunLoop 会让线程从用户态切换到内核态并进入休眠(Sleep),此时 CPU 不占用资源。
    • 当有事件到来(如触摸或网络数据),内核会将线程唤醒,处理任务。这极大地延长了移动设备的电池寿命。
  4. 管理 AutoreleasePool

    • RunLoop 在每次循环的休眠之前会释放旧的 AutoreleasePool,并在唤醒后创建新的 AutoreleasePool。这保证了在循环中产生的临时对象能被及时释放,防止内存暴涨。

二、 RunLoop 的 Mode (模式)

RunLoop 的运行是基于 Mode 的。

1. Mode 的概念

  • 一个 RunLoop 包含多个 Mode。
  • 每个 Mode 内部包含一组 Source (事件源)、Timer (定时器) 和 Observer (监听者)。
  • 关键规则:RunLoop 在同一时刻只能运行在一种 Mode 下。如果要切换 Mode,必须退出当前 Loop,重新指定 Mode 进入。这样做是为了隔离不同组的 Source/Timer/Observer,互不影响。

2. 常见的 Mode 类型

系统默认注册了 5 个 Mode,开发者最常接触的是前三个:

  1. kCFRunLoopDefaultMode (NSDefaultRunLoopMode)

    • 描述:App 的默认运行模式。
    • 场景:通常主线程是在这个 Mode 下运行的。绝大多数空闲状态或非滑动状态下,App 都在此模式。
  2. UITrackingRunLoopMode

    • 描述:界面跟踪 Mode。
    • 场景:当用户手指在屏幕上滑动(如滑动 UIScrollViewUITableView)时,RunLoop 会自动切换到这个模式。
    • 目的:为了保证界面滑动的流畅性,该模式拥有最高优先级,会暂停处理 DefaultMode 下的低优先级事件(如普通的 Timer)。
  3. kCFRunLoopCommonModes (NSRunLoopCommonModes)

    • 描述:这不是一个真正的、具体的运行 Mode,而是一个集合(标记)
    • 作用:默认情况下,它包含了 DefaultModeTrackingMode
    • 应用:如果你将一个 Timer 添加到 CommonModes,意味着这个 Timer 在“默认模式”和“滑动模式”下都能运行。
  4. UIInitializationRunLoopMode

    • 描述:私有 Mode。
    • 场景:App 启动时的第一个 Mode,启动完成后就不再使用。
  5. GSEventReceiveRunLoopMode

    • 描述:内部 Mode,用于接受系统内部事件(Graphics Services),通常开发者用不到。

三、 经典面试题/实战场景:Timer 在滑动时停止工作

问题描述:
你在 viewDidLoad 中创建了一个 NSTimer 用于轮播图自动滚动。但是当你手指按住 UITableViewUIScrollView 滑动时,轮播图停止了滚动。当你松手停止滑动后,轮播图又恢复了。

原因分析:

  1. 默认情况下,NSTimer 是被添加到 NSDefaultRunLoopMode 的。
  2. 当你滑动屏幕时,主线程的 RunLoop 切换到了 UITrackingRunLoopMode
  3. 因为 RunLoop 同一时间只能运行在一个 Mode 下,且不同 Mode 互不干扰,所以处于 DefaultMode 的 Timer 就不会被回调了。

解决方案:
将 Timer 添加到 NSRunLoopCommonModes 中。这样无论是默认状态还是滑动状态,Timer 都能正常工作。

plaintext
// Objective-C
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
plaintext
// Swift
let timer = Timer(timeInterval: 1.0, target: self, selector: #selector(tick), userInfo: nil, repeats: true)
RunLoop.current.add(timer, forMode: .common)

总结

  • 基本作用:保活线程、处理事件、节省资源(休眠/唤醒机制)、管理自动释放池。
  • 核心 Mode
    • Default:平时用。
    • Tracking:滑动时用(优先级高)。
    • Common:占位符,相当于 Default + Tracking,用于解决滑动卡顿/停止问题。
00:00
00:00