You need to enable JavaScript to run this app. SwiftUI中如何在自定义类的Timer内执行来自View的Void动作
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中如何在自定义类的Timer内执行来自View的Void动作

崩溃根因排查
  • 核心触发崩溃的原因:Timer 采用 target-selector 模式触发回调时,系统会将当前触发的 Timer 实例作为唯一参数传入对应 selector 方法。但你定义的 @objc func timerFunction(action:) 要求入参是一个 () -> Void 类型的闭包,当 Timer 首次触发时,系统会把传入的 Timer 对象的内存地址强行当成闭包函数指针执行,直接触发 EXC_BAD_ACCESS 野指针访问错误。
  • 逻辑疏漏:你原本希望把 SwiftUI 视图传入的 Void 闭包在 Timer 每轮触发时执行,但代码完全没有把闭包存储为类成员属性——点击启动按钮时手动调用的那次 timerFunction(action: { print("Hello") }) 只是单次同步执行了闭包,后续 Timer 触发逻辑和这个闭包没有任何关联。
  • 冗余操作:Timer.scheduledTimer(...) 初始化时已经自动将 timer 挂载到当前 RunLoop 的默认模式,代码中手动重复执行 RunLoop.current.add(timer, forMode: .common) 会导致 timer 被多次挂载到 RunLoop,出现触发次数异常。
可运行的修复实现

优先使用闭包版本的 Timer 初始化接口,替代容易出现签名不匹配的 target-selector 模式,同时正确存储外部传入的回调,避免循环引用:

class CustomTimer: ObservableObject {
    // 存储外部传入的执行动作
    private var timerAction: (() -> Void)?
    private var timer: Timer?

    /// 给定时器绑定要执行的回调
    func bindAction(_ action: @escaping () -> Void) {
        timerAction = action
    }

    func startTimer(timeInterval: Double) {
        // 启动前先销毁旧定时器,避免多实例并行
        stopTimer()
        // 闭包版Timer初始化,系统会自动处理参数匹配,不会出现野指针问题
        timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { [weak self] _ in
            // 触发时执行预先绑定的回调
            self?.timerAction?()
        }
        timer?.tolerance = 0.0
        // 按需补充common模式挂载,避免ScrollView滑动时定时器进入暂停状态
        if let validTimer = timer {
            RunLoop.current.add(validTimer, forMode: .common)
        }
    }
    
    func stopTimer() {
        timer?.invalidate()
        timer = nil
    }
}

对应视图层的调用逻辑同步修正:

struct ContentView: View {
    @StateObject var customTimer = CustomTimer()

    var body: some View {
        VStack(spacing: 30.0) {
            Button("start timer") {
                // 先绑定要执行的动作
                customTimer.bindAction {
                    print("Hello")
                }
                // 再启动定时器
                customTimer.startTimer(timeInterval: 1.0)
            }
            
            Button("stop timer") {
                customTimer.stopTimer()
            }
        }
        .font(Font.body.bold())
    }
}
落地注意事项
  • 初始化闭包版 Timer 时必须添加 [weak self] 捕获列表,否则闭包会强持有 CustomTimer 实例,造成页面销毁后定时器仍在后台运行的内存泄漏。
  • 每次启动新定时器前必须先执行 stopTimer() 释放旧实例,否则会叠加多个并行运行的定时器,导致回调执行次数翻倍。
  • 如果坚持使用 target-selector 模式实现,必须保证 selector 方法签名和系统传参完全匹配——正确的方法签名应为 @objc func timerDidFire(_ sender: Timer),在方法内部调用预先存储的自定义闭包,否则必然会出现本次的野指针崩溃。

内容的提问来源于stack exchange,提问作者swiftPunk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.25 04:36:03