RunLoop 在实际开发中的应用

  • Post category:未分类

最近在准备 iOS 相关的面试题,打算从实际应用中学习、掌握基本概念

RunLoop与线程的关系:每个线程都有一个唯一的RunLoop对象,主线程的RunLoop默认是启动的,而子线程的RunLoop需要手动启动

RunLoop的模式(Mode):RunLoop在不同的模式下运行,每个模式包含一组输入源(Source)、定时器(Timer)和观察者(Observer)。常见的模式有:
NSDefaultRunLoopMode:默认模式,空闲状态。
UITrackingRunLoopMode:滚动时的模式。
NSRunLoopCommonModes:一个通用模式集合,包括前两种。

RunLoop的事件源:
Source0:非基于Port的事件,需要手动触发。
Source1:基于Port的事件,通过内核和其他线程通信。
Timers:定时器事件。
Observers:观察者,监听RunLoop的状态变化。

一:保持线程常驻


class PersistentThread: Thread {
    var runLoop: RunLoop?

    override func main() {
        autoreleasepool {
            // 获取当前线程的RunLoop
            self.runLoop = RunLoop.current

            // 添加Port防止RunLoop立即退出
            let port = Port()
            self.runLoop?.add(port, forMode: .default)

            print("子线程RunLoop启动")

            // 启动RunLoop,线程会保持活跃
            self.runLoop?.run()

            print("子线程RunLoop退出")
        }
    }
}

// 使用示例
class ViewController: UIViewController {
    var persistentThread: PersistentThread?

    func example1() {
        persistentThread = PersistentThread()
        persistentThread?.start()

        // 给线程一点时间启动RunLoop
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            // 在常驻线程执行任务
            self.perform(#selector(self.doBackgroundTask),
                        on: self.persistentThread!,
                        with: nil,
                        waitUntilDone: false)
        }
    }

    @objc func doBackgroundTask() {
        print("在常驻线程执行耗时任务: \(Thread.current)")
    }
}

二:处理Timer的滚动冲突


class TimerViewController: UIViewController {
    var timer1: Timer?
    var timer2: Timer?
    var count = 0

    func setupTimer() {
        // 方式1:默认模式 - 滚动时暂停
        timer1 = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.timerFired()
        }
        // 默认已经添加到DefaultMode

        // 方式2:CommonModes - 滚动时也触发
        timer2 = Timer(timeInterval: 1.0, repeats: true) { [weak self] _ in
            self?.timerFired()
        }
        RunLoop.current.add(timer2!, forMode: .common)
    }

    func timerFired() {
        count += 1
        print("定时器触发: \(count)")
    }

    // Swift中的简化版本(iOS 10+推荐)
    func setupModernTimer() {
        // 使用Timer的block API(iOS 10+)
        _ = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
            print("现代Timer API")
        }
    }
}

检测卡顿

import Foundation

class LagMonitor {
    private var semaphore: DispatchSemaphore?
    private var activity: CFRunLoopActivity = .entry
    private var isMonitoring = false

    func startMonitoring() {
        guard !isMonitoring else { return }
        isMonitoring = true

        semaphore = DispatchSemaphore(value: 0)

        // 观察主线程RunLoop
        var context = CFRunLoopObserverContext(
            version: 0,
            info: Unmanaged.passUnretained(self).toOpaque(),
            retain: nil,
            release: nil,
            copyDescription: nil
        )

        guard let observer = CFRunLoopObserverCreate(
            kCFAllocatorDefault,
            CFRunLoopActivity.allActivities.rawValue,
            true,
            0,
            runLoopObserverCallBack,
            &context
        ) else { return }

        CFRunLoopAddObserver(RunLoop.main.getCFRunLoop(), observer, .commonModes)

        // 在子线程监控时长
        DispatchQueue.global(qos: .background).async { [weak self] in
            guard let self = self else { return }

            while self.isMonitoring {
                // 等待信号量,如果超时50ms(1/20秒)说明可能卡顿
                let waitResult = self.semaphore?.wait(timeout: .now() + .milliseconds(50))

                if waitResult == .timedOut { // 超时
                    if self.activity == .beforeSources || 
                       self.activity == .afterWaiting {
                        // 注意:这里要回到主线程处理UI相关操作
                        DispatchQueue.main.async {
                            print("⚠️ 检测到卡顿!活动状态: \(self.activity.rawValue)")
                            // 可以在这里记录堆栈信息或上报
                        }
                    }
                }
            }
        }
    }

    func stopMonitoring() {
        isMonitoring = false
    }

    private func runLoopObserverCallBack(activity: CFRunLoopActivity) {
        self.activity = activity
        self.semaphore?.signal()
    }
}

// 全局函数作为CFRunLoopObserver的回调
private func runLoopObserverCallBack(
    observer: CFRunLoopObserver?,
    activity: CFRunLoopActivity,
    info: UnsafeMutableRawPointer?
) {
    guard let info = info else { return }
    let monitor = Unmanaged.fromOpaque(info).takeUnretainedValue()
    monitor.runLoopObserverCallBack(activity: activity)
}