NSTimer (現 Timer) 的 userInfo 參數是定時器回調中傳遞上下文數據的核心機制。本文詳細講解其類型本質、常見用法模式、類型安全風險及 Swift 現代化封裝方案。
// 創建定時器時傳遞 userInfo
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(timerFired(_:)), userInfo: someObject, repeats: true)
// 回調中接收
@objc func timerFired(_ timer: Timer) {
let btn = timer.userInfo as? UIButton // 類型轉換
btn?.setTitle("Clicked", for: .normal)
}
本質:userInfo 是 Any? (OC 中 id _Nullable),可攜帶任意對象引用。定時器內部強引用 userInfo,直到定時器失效釋放。
timer.userInfo as! MyClass 若類型不匹配直接崩潰。必須用 as? 可選綁定。Int、Struct 需裝箱 NSNumber、NSValue 或自定義類。userInfo 持有 self,且 self 持有定時器 → 內存洩漏。解決:userInfo 傳遞弱引用封裝或不傳 self。userInfo 未正確區分導致邏輯錯誤。
final class TypedTimer {
private var timer: Timer?
private let handler: (Context) -> Void
init(interval: TimeInterval, repeats: Bool, context: Context, handler: @escaping (Context) -> Void) {
self.handler = handler
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: repeats) { [weak self] _ in
self?.handler(context)
}
}
func invalidate() { timer?.invalidate(); timer = nil }
deinit { invalidate() }
}
// 使用示例
let btn = UIButton()
let timer = TypedTimer(interval: 1.0, repeats: true, context: btn) { button in
button.setTitle("⏱ \(Date())", for: .normal)
}
// 無需類型轉換,上下文類型安全,自動釋放
@selector 回調遷移為基於閉包的 Timer.scheduledTimer(withTimeInterval:repeats:block:) (iOS 10+)。WeakProxy 封裝 target 避免定時器強引用導致控制器不釋放。TypedTimer 或 Combine 框架 (Timer.publish) 替代裸 NSTimer。