mcpField
A
axiom-timer-patterns-ref

axiom-timer-patterns-ref

github.com/charleswiltgen开发1.2k87 次安装今天更新
代码仓库

关于 axiom-timer-patterns-ref

面向 Swift 开发者的 iOS 计时器 API 参考技能,涵盖 Timer、DispatchSourceTimer、Combine 和并发计时器,以及生命周期、取消与调试。

主要功能

  1. 01Timer 与 RunLoop 模式参考
  2. 02DispatchSourceTimer 生命周期与宽限时间
  3. 03Combine 计时器发布与取消
  4. 04异步计时器和 Task.sleep 模式
  5. 05LLDB 计时器检查命令

适用场景

  1. 01为重复任务选择计时器 API
  2. 02避免计时器引用循环和生命周期错误
  3. 03实现可取消的异步轮询
  4. 04使用 LLDB 调试计时器行为

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/charleswiltgen/axiom --skill axiom-timer-patterns-ref

安装后在 Claude Code 中输入/axiom-timer-patterns-ref确认已加载。

详细介绍

定时器模式参考

iOS 定时器机制的完整 API 参考。有关决策树和崩溃防护,请参阅 axiom-timer-patterns。


第 1 部分:定时器 API

Timer.scheduledTimer(基于闭包)
// Most common — block-based, auto-added to current RunLoop
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
    self?.updateProgress()
}

关键细节:添加到 .default RunLoop 模式。滚动期间会停止。请参阅下方第 1 部分的 RunLoop 模式表。

Timer.scheduledTimer(基于选择器)
// Objective-C style — RETAINS TARGET (leak risk)
let timer = Timer.scheduledTimer(
    timeInterval: 1.0,
    target: self,       // Timer retains self!
    selector: #selector(update),
    userInfo: nil,
    repeats: true
)

危险:此 API 会保留 target。如果 self 也持有该定时器,就会形成循环引用。使用带有 [weak self] 的基于闭包的 API 始终更安全。

Timer.init(手动添加到 RunLoop)
// Create timer without adding to RunLoop
let timer = Timer(timeInterval: 1.0, repeats: true) { [weak self] _ in
    self?.updateProgress()
}

// Add to specific RunLoop mode
RunLoop.current.add(timer, forMode: .common)  // Survives scrolling
timer.tolerance
timer.tolerance = 0.1  // Allow 100ms flexibility for system coalescing

设置容差后,系统会将触发时间相近的定时器合并处理。建议的最小值:间隔的 10%。这可以减少 CPU 唤醒次数和能耗。

RunLoop 模式
模式常量激活时机定时器会触发吗?
默认.default / RunLoop.Mode.default正常用户交互是
跟踪.tracking / RunLoop.Mode.tracking滚动/拖动手势处于活动状态仅在添加到 .common 时
通用.common / RunLoop.Mode.common伪模式(默认 + 跟踪)是(始终)
timer.invalidate()
timer.invalidate()  // Stops timer, removes from RunLoop
// Timer is NOT reusable after invalidate — create a new one
timer = nil          // Release reference

关键细节:必须在创建定时器的同一线程上调用 invalidate()(通常是主线程)。

timer.isValid
if timer.isValid {
    // Timer is still active
}

调用 invalidate() 后,或非重复定时器触发后,返回 false。

Timer.publish(Combine)
Timer.publish(every: 1.0, tolerance: 0.1, on: .main, in: .common)
    .autoconnect()
    .sink { [weak self] _ in
        self?.updateProgress()
    }
    .store(in: &cancellables)

有关 Combine 定时器的完整详情,请参阅第 3 部分。


第 2 部分:DispatchSourceTimer API

创建
// Create timer source on a specific queue
let queue = DispatchQueue(label: "com.app.timer")
let timer = DispatchSource.makeTimerSource(flags: [], queue: queue)

flags:通常为空([])。需要精确计时时使用 .strict(这会禁用系统合并处理,但能耗更高)。

调度
// Relative deadline (monotonic clock)
timer.schedule(
    deadline: .now() + 1.0,     // First fire
    repeating: .seconds(1),     // Interval
    leeway: .milliseconds(100)  // Tolerance (like Timer.tolerance)
)

// Wall clock deadline (survives device sleep)
timer.schedule(
    wallDeadline: .now() + 1.0,
    repeating: .seconds(1),
    leeway: .milliseconds(100)
)

deadline 与 wallDeadline:deadline 使用单调时钟(设备休眠时会暂停)。wallDeadline 使用挂钟(休眠期间仍继续计时)。大多数情况下请使用 deadline。

事件处理程序
timer.setEventHandler { [weak self] in
    self?.performWork()
}

取消前:将处理程序设为 nil,以打破循环引用:

timer.setEventHandler(handler: nil)
timer.cancel()
生命周期方法
timer.activate()   // Start — can only call ONCE (idle → running)
timer.suspend()    // Pause (running → suspended)
timer.resume()     // Unpause (suspended → running)
timer.cancel()     // Stop permanently (must NOT be suspended)
状态机生命周期
                    activate()
        idle ──────────────► running
                               │  ▲
                    suspend()  │  │  resume()
                               ▼  │
                            suspended
                               │
                    resume() + cancel()
                               │
                               ▼
                           cancelled

关键规则:

  • activate() 只能调用一次(idle → running)
  • cancel() 要求处于非挂起状态(如果处于挂起状态,请先恢复)
  • cancelled 是终止状态——之后不得再执行任何操作
  • 释放时必须处于非挂起状态(如有需要,请先取消)
Leeway(容差)
// Leeway values
timer.schedule(deadline: .now(), repeating: 1.0, leeway: .milliseconds(100))
timer.schedule(deadline: .now(), repeating: 1.0, leeway: .seconds(1))
timer.schedule(deadline: .now(), repeating: 1.0, leeway: .never)  // Strict — high energy

Leeway 相当于 DispatchSourceTimer 版本的 Timer.tolerance。它允许系统合并定时器触发,以提高能效。

端到端示例

在一个代码块中展示完整的 DispatchSourceTimer 生命周期:

let queue = DispatchQueue(label: "com.app.polling")
let timer = DispatchSource.makeTimerSource(queue: queue)
timer.schedule(deadline: .now() + 1.0, repeating: .seconds(5), leeway: .milliseconds(500))
timer.setEventHandler { [weak self] in
    self?.fetchUpdates()
}
timer.activate()  // idle → running

// Later — pause:
timer.suspend()   // running → suspended

// Later — resume:
timer.resume()    // suspended → running

// Cleanup — MUST resume before cancel if suspended:
timer.setEventHandler(handler: nil)  // Break retain cycles
timer.resume()    // Ensure non-suspended state
timer.cancel()    // running → cancelled (terminal)

如需可防止所有崩溃模式的安全封装,请参阅 axiom-timer-patterns 第 4 部分:SafeDispatchTimer。


第 3 部分:Combine 定时器

Timer.publish
import Combine

// Create publisher — RunLoop mode matters here too
let publisher = Timer.publish(
    every: 1.0,          // Interval
    tolerance: 0.1,      // Optional tolerance
    on: .main,           // RunLoop
    in: .common          // Mode — use .common to survive scrolling
)
.autoconnect()
// Starts immediately when first subscriber attaches
Timer.publish(every: 1.0, on: .main, in: .common)
    .autoconnect()
    .sink { date in
        print("Fired at \(date)")
    }
    .store(in: &cancellables)
.connect()(手动启动)
// Manual control over when timer starts
let timerPublisher = Timer.publish(every: 1.0, on: .main, in: .common)
let cancellable = timerPublisher
    .sink { date in
        print("Fired at \(date)")
    }

// Start later
let connection = timerPublisher.connect()

// Stop
connection.cancel()
取消
// Via AnyCancellable storage — cancelled when Set is cleared or object deallocs
private var cancellables = Set<AnyCancellable>()

// Manual cancellation
cancellables.removeAll()  // Cancels all subscriptions
SwiftUI 集成
class TimerViewModel: ObservableObject {
    @Published var elapsed: Int = 0
    private var cancellables = Set<AnyCancellable>()

    func start() {
        Timer.publish(every: 1.0, tolerance: 0.1, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] _ in
                self?.elapsed += 1
            }
            .store(in: &cancellables)
    }

    func stop() {
        cancellables.removeAll()
    }
}

第 4 部分:AsyncTimerSequence(Swift 并发)

ContinuousClock.timer
// Monotonic clock — does NOT pause when app suspends
for await _ in ContinuousClock().timer(interval: .seconds(1)) {
    await updateData()
}
// Loop exits when task is cancelled
SuspendingClock.timer
// Suspending clock — pauses when app suspends
for await _ in SuspendingClock().timer(interval: .seconds(1)) {
    await processItem()
}

ContinuousClock 与 SuspendingClock 的区别:

  • ContinuousClock:应用挂起期间,时间仍会继续流逝。适用于绝对计时。
  • SuspendingClock:应用挂起时,时间会暂停。适用于“用户感知”的计时。
任务取消
// Timer automatically stops when task is cancelled
let timerTask = Task {
    for await _ in ContinuousClock().timer(interval: .seconds(1)) {
        await fetchLatestData()
    }
}

// Later: cancel the timer
timerTask.cancel()
使用结构化并发进行后台轮询
func startPolling() async {
    do {
        for try await _ in ContinuousClock().timer(interval: .seconds(30)) {
            try Task.checkCancellation()
            let data = try await api.fetchUpdates()
            await MainActor.run { updateUI(with: data) }
        }
    } catch is CancellationError {
        // Clean exit
    } catch {
        // Handle fetch error
    }
}

第 5 部分:Task.sleep 替代方案

单次延迟
// Simple delay — NOT a timer
try await Task.sleep(for: .seconds(1))

// Deadline-based
try await Task.sleep(until: .now + .seconds(1), clock: .continuous)
何时使用 Sleep,何时使用定时器
需求使用
执行操作前的一次性延迟Task.sleep(for:)
重复执行操作ContinuousClock().timer(interval:)
可取消的延迟在 Task 中使用 Task.sleep(for:)
带退避的重试在循环中使用 Task.sleep(for:)
指数退避重试
func fetchWithRetry(maxAttempts: Int = 3) async throws -> Data {
    var delay: Duration = .seconds(1)
    for attempt in 1...maxAttempts {
        do {
            return try await api.fetch()
        } catch where attempt < maxAttempts {
            try await Task.sleep(for: delay)
            delay *= 2  // Exponential backoff
        }
    }
    throw FetchError.maxRetriesExceeded
}

第 6 部分:LLDB 定时器检查

Timer(NSTimer)命令
# Check if timer is still valid
po timer.isValid

# See next fire date
po timer.fireDate

# See timer interval
po timer.timeInterval

# Force RunLoop iteration (may trigger timer)
expression -l objc -- (void)[[NSRunLoop mainRunLoop] run]
DispatchSourceTimer 命令
# Inspect dispatch source
po timer

# Break on dispatch source cancel (all sources)
breakpoint set -n dispatch_source_cancel

# Break on EXC_BAD_INSTRUCTION to catch timer crashes
# (Xcode does this automatically for Swift runtime errors)

# Check if a DispatchSource is cancelled
expression -l objc -- (long)dispatch_source_testcancel((void*)timer)
通用定时器调试
# List all timers on the main RunLoop
expression -l objc -- (void)CFRunLoopGetMain()

# Break when any Timer fires
breakpoint set -S "scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:"

第 7 部分:平台可用性矩阵

APIiOSmacOSwatchOStvOS
Timer2.0+10.0+2.0+9.0+
DispatchSourceTimer8.0+ (GCD)10.10+2.0+9.0+
Timer.publish (Combine)13.0+10.15+6.0+13.0+
AsyncTimerSequence16.0+13.0+9.0+16.0+
Task.sleep13.0+10.15+6.0+13.0+

相关技能

  • axiom-timer-patterns — 决策树、崩溃模式、SafeDispatchTimer 封装
  • axiom-energy — 将 Timer 容差作为能耗优化手段(模式 1)
  • axiom-energy-ref — Timer 效率 API,附 WWDC 代码示例
  • axiom-memory-debugging — Timer 作为模式 1 内存泄漏

资源

技能:axiom-timer-patterns、axiom-energy-ref、axiom-memory-debugging

评论

支持 Markdown