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
相关产品推荐
相关产品推荐

