Swift中Timer失效后重新初始化无法正常工作的问题
I've run into this exact issue before—let's break down what's going wrong and how to fix it quickly.
The Root Cause
When you call invalidate() on your itemPlayTimer, the timer gets removed from the RunLoop, but your itemPlayTimer variable still holds a reference to the invalidated timer instance. That means when you call startTimer() again, the check if itemPlayTimer != nil { return } skips creating a new timer entirely.
Step-by-Step Solutions
1. Always Nil Out the Timer After Invalidation
First, update your pause logic to not just invalidate the timer, but set the variable to nil:
func pausePlayerAndTimer() { // Pause your audio player first player?.pause() // Invalidate and nil the timer itemPlayTimer?.invalidate() itemPlayTimer = nil // Critical line! }
2. Refine the startTimer() Logic
To make your timer initialization more robust, add a cleanup step at the start of startTimer()—this ensures no stale timer references are left behind:
func startTimer() { print("PlayerController: startTimer()") // Clean up any existing timer first itemPlayTimer?.invalidate() itemPlayTimer = nil // Create the new timer itemPlayTimer = Timer.scheduledTimer( timeInterval: 0.001, target: self, selector: #selector(updateItemPlayerTimer), userInfo: nil, repeats: true ) // Optional: Add to .common RunLoop mode to keep updating during UI interactions (like scrolling) RunLoop.current.add(itemPlayTimer!, forMode: .common) }
3. Bonus: Avoid Circular References with Block-Based Timers
The selector-based timer can create a circular reference between self and the timer. Switch to a block-based timer with a weak reference to self to fix this:
func startTimer() { print("PlayerController: startTimer()") itemPlayTimer?.invalidate() itemPlayTimer = nil itemPlayTimer = Timer.scheduledTimer(withTimeInterval: 0.001, repeats: true) { [weak self] timer in guard let self = self, let currentTime = self.player?.currentTime else { // Invalidate the timer if we lose reference to self or the player timer.invalidate() return } self.updateTimeDescription?(currentTime) } RunLoop.current.add(itemPlayTimer!, forMode: .common) }
Why This Works
- Nil-ing out
itemPlayTimerafter invalidation ensures yourstartTimer()check doesn't skip creating a new timer. - Adding the timer to
.commonRunLoop mode ensures it keeps firing even when the user is interacting with the UI (like scrolling a table view). - Block-based timers with
[weak self]prevent memory leaks that could cause unexpected behavior down the line.
内容的提问来源于stack exchange,提问作者Dmitry Kuleshov

