Swift MVC模式下结构体中Timer的正确实现方法
Swift MVC重构秒表Timer无法运行的解决方案
问题背景
Swift新手开发秒表应用,原代码全部放在ViewController中时Timer可正常运行;改用MVC模式重构,将Timer逻辑放入结构体FastBrain后报错:'weak'仅适用于类及类约束协议类型,而非'FastBrain',调整后仍无法正常启动Timer。
原ViewController可运行代码片段
fastTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] timer in // Update the elapsed time variable self?.elapsedTime += timer.timeInterval // Format the elapsed time as a stopwatch time //calculate values for timer label for next run let hours = Int(self?.elapsedTime ?? 0) / 3600 % 60 let minutes = Int(self?.elapsedTime ?? 0) / 60 % 60 let seconds = Int(self?.elapsedTime ?? 0) % 60 //if fast is completed don't recalculate progress bar if self?.progressBar.progress ?? 0.0 >= 1.0 { self?.progressBar.progress = 1 } else { //calculate value for progress bar self?.progressBar.progress = Float(self?.elapsedTime ?? 0.0) / self!.goalFastTimeSeconds } // Update the label with the formatted time self?.timeElapsedLabel.text = String(format: "%02d:%02d:%02d", hours, minutes, seconds) }) // Update the button text as we've started fast sender.setTitle("Finish Fast", for: .normal)
当前模型结构体FastBrain代码
import Foundation struct FastBrain { var goalFastTimeHours = 1 var goalFastTimeSeconds: Float = 3600 var progress: Float = 0.0 var fastTimer: Timer? var elapsedTime: TimeInterval = 0 let startDate: Date = Date() let dateFormatter = DateFormatter() var hours = 0 var minutes = 0 var seconds = 0 mutating func startFast(){ //start the timer - except it won't :-( fastTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] timer in // Update the elapsed time variable elapsedTime += fastTimer?.timeInterval ?? 0.0 //calculate values for timer label for next run hours = Int(self?.elapsedTime ?? 0 ) / 3600 % 60 minutes = Int(self?.elapsedTime ?? 0 ) / 60 % 60 seconds = Int(self?.elapsedTime ?? 0) % 60 //if fast is completed don't recalculate progress bar if progress >= 1.0 { progress = 1 } else { //calculate value for progress bar progress = Float(self?.elapsedTime ?? 0.0 ) / (self?.goalFastTimeSeconds ?? 0) } //How do I call this function from here? // updateUIDuringFast() })//block ends here } }
核心问题分析
- 结构体不能使用
weak引用:FastBrain是值类型(结构体),[weak self]仅适用于引用类型(类),值类型不存在引用计数,weak无意义。 - 值类型状态更新失效:Timer闭包捕获结构体实例时,会创建副本,闭包内修改的是副本状态,无法同步到外部的原实例。
- 模型违反MVC原则:原代码在模型中尝试处理UI更新,MVC模式下模型应只负责业务逻辑,UI更新由ViewController处理。
修正方案
1. 将FastBrain改为类(引用类型)
把结构体改为类,解决weak self的类型限制,同时保证闭包修改的是同一个实例的状态:
import Foundation class FastBrain { var goalFastTimeHours = 1 var goalFastTimeSeconds: Float = 3600 var progress: Float = 0.0 var fastTimer: Timer? var elapsedTime: TimeInterval = 0 let startDate: Date = Date() let dateFormatter = DateFormatter() var hours = 0 var minutes = 0 var seconds = 0 // 定义回调闭包,用于通知ViewController更新UI var updateHandler: (() -> Void)? func startFast(){ // 先销毁已有Timer,避免重复创建 fastTimer?.invalidate() // 重置计时状态 elapsedTime = 0 progress = 0 fastTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { [weak self] timer in guard let self = self else { timer.invalidate() // 实例已释放,停止Timer return } // 更新累计计时 self.elapsedTime += timer.timeInterval // 计算时分秒 self.hours = Int(self.elapsedTime) / 3600 % 60 self.minutes = Int(self.elapsedTime) / 60 % 60 self.seconds = Int(self.elapsedTime) % 60 // 更新进度,完成后停止Timer if self.progress >= 1.0 { timer.invalidate() self.progress = 1 } else { self.progress = Float(self.elapsedTime) / self.goalFastTimeSeconds } // 触发回调,通知UI更新 self.updateHandler?() }) } // 添加停止Timer的方法 func stopFast() { fastTimer?.invalidate() fastTimer = nil } }
2. ViewController中使用FastBrain并处理UI更新
在ViewController中初始化FastBrain实例,通过回调闭包处理UI更新,同时管理按钮交互:
class ViewController: UIViewController { @IBOutlet weak var timeElapsedLabel: UILabel! @IBOutlet weak var progressBar: UIProgressView! @IBOutlet weak var startButton: UIButton! // 初始化FastBrain类实例 let fastBrain = FastBrain() override func viewDidLoad() { super.viewDidLoad() // 设置模型的回调,处理UI更新逻辑 fastBrain.updateHandler = { [weak self] in guard let self = self else { return } // 更新时间显示标签 self.timeElapsedLabel.text = String(format: "%02d:%02d:%02d", self.fastBrain.hours, self.fastBrain.minutes, self.fastBrain.seconds) // 更新进度条 self.progressBar.progress = self.fastBrain.progress // 计时完成后更新按钮文字 if self.fastBrain.progress >= 1.0 { self.startButton.setTitle("Restart Fast", for: .normal) } } } @IBAction func startButtonTapped(_ sender: UIButton) { if fastBrain.fastTimer?.isValid ?? false { // 当前正在计时,停止并切换按钮文字 fastBrain.stopFast() sender.setTitle("Start Fast", for: .normal) } else { // 开始计时并切换按钮文字 fastBrain.startFast() sender.setTitle("Finish Fast", for: .normal) } } // 页面消失时停止Timer,避免后台泄漏 override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) fastBrain.stopFast() } }
关键改进点
- 将
FastBrain改为类,解决weak self的类型问题,保证状态修改的一致性。 - 新增
updateHandler回调,让模型专注业务逻辑,UI更新由ViewController处理,符合MVC原则。 - 添加
stopFast方法,避免Timer重复创建和后台资源泄漏。 - 在Timer闭包中增加
guard let self = self,安全处理实例释放的情况,避免野指针访问。
内容的提问来源于stack exchange,提问作者CatherineLeBlanc
相关产品推荐
相关产品推荐

