【发布时间】:2018-01-12 16:27:10
【问题描述】:
我正在使用 MVVM 创建一个举重计算器应用程序 (Swift 4),并且已经尝试了 2 天来弄清楚为什么应该死掉的视图模型仍然响应 UserDefaults.defaultsDidChange 事件通知。
- 我启动应用程序:
- 在启动时,我在 AppDelegate 中创建了一个新的 lift 事件对象,并使用它为 `CalculatorViewController' 初始化一个新的
CalculatorLiftEventViewModelFromLiftEvent:
- 在启动时,我在 AppDelegate 中创建了一个新的 lift 事件对象,并使用它为 `CalculatorViewController' 初始化一个新的
- 计算升力并保存
- 我点击 + 按钮来创建一个新的电梯:
- 这会导致创建一个新的空电梯事件对象
- 这个新的提升事件对象用于初始化一个新的
CalculatorLiftEventViewModelFromLiftEvent对象 - 然后将这个新的
CalculatorLiftEventViewModelFromLiftEvent分配给CalculatorViewController的viewModel 属性,替换应用启动时创建的那个 - 计算器屏幕上的值已清零,准备输入新的电梯事件
- 我点击“设置”按钮转到“设置”,在其中更改与当前提升事件关联的公式。
- 新公式保存为默认值并触发
UserDefaults.defaultsDidChange通知 - 这是我无法弄清楚的部分:原始视图模型仍然存在,并且仍在侦听 UserDefault 通知。当我关闭“设置”屏幕并返回“计算器”视图时,先前已清除的提升事件中的值现在重新出现。
点击计算器屏幕上的 +(新)按钮时会发生以下情况:
@objc fileprivate func onNewButtonTapped(_ sender: UIBarButtonItem) {
let newLiftEvent = dataManager.createNewLiftEvent()
viewModel = CalculatorLiftEventViewModelFromLiftEvent(withLiftEvent: newLiftEvent, dataManager: dataManager)
setupView()
}
CalculatorLiftEventViewModelFromLiftEvent 的初始化方式如下:
init(withLiftEvent liftEvent: LiftEventRepresentable, dataManager: CoreDataHelper) {
self.modelLiftEvent = liftEvent
self.liftName = Dynamic("\(modelLiftEvent.lift.liftName)")
self.weightLiftedTextField = Dynamic(modelLiftEvent.liftWeight.value)
self.repetitionsTextField = Dynamic("\(modelLiftEvent.repetitions)")
self.oneRepMaxTextField = Dynamic(modelLiftEvent.oneRepMax.value)
self.unitsTextField = Dynamic("\(UserDefaults.weightUnit())")
self.weightPercentages = Dynamic( [ : ] )
self.dataManager = dataManager
super.init()
subscribeToNotifications()
}
更新:这是deinit 和CalculatorLiftEventViewModelFromLiftEvent 中的addObservers。请注意,我没有使用基于块的观察。
deinit {
print("I got to the deinit method")
unsubscribeFromNotifications()
}
func subscribeToNotifications() {
NotificationCenter.default.addObserver(self,
selector: #selector(liftNameDidChangeNotification(_:)),
name: NSNotification.Name(rawValue: LiftEventNotifications.LiftNameDidChangeNotification),
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(weightUnitDidChangeNotification(_:)),
name: NSNotification.Name(rawValue: LiftEventNotifications.WeightUnitDidChangeNotification),
object: nil)
NotificationCenter.default.addObserver(self,
selector: #selector(roundingOptionDidChangeNotification(_:)),
name: NSNotification.Name(rawValue: UserDefaultsNotifications.roundingOptionDidChangeNotification),
object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.defaultsDidChange), name: UserDefaults.didChangeNotification,
object: nil)
}
--- 结束更新
我在转到SettingsViewController 时传递了modelLiftEvent:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case a:...
case b:...
case "SettingsSegue":
if let nav = segue.destination as? UINavigationController {
let destinationViewController = nav.topViewController as! SettingsViewController
destinationViewController.dismissalDelegate = self
let settingsViewModel = SettingsViewModelFromLiftEvent(withLiftEvent: self.viewModel.modelLiftEvent)
destinationViewController.settingsViewModel = settingsViewModel
destinationViewController.dataManager = dataManager
settingsViewModel.dataManager = dataManager
}
最后,在CalculatorLiftEventViewModelFromLiftEvent 中,我在这里设置了一个断点,因为当视图模型听到UserDefaults.defaultsDidChange 通知时会调用它。此时,我也验证了这个CalculatorLiftEventViewModelFromLiftEvent是旧的,而不是我点击+按钮时创建的新的:
@objc func defaultsDidChange(_ notification: Notification) {
let oneRepMax = modelLiftEvent.calculateOneRepMax()
guard oneRepMax.value != 0.0 else { return }
let weightPercentages = getWeightPercentages(weight: oneRepMax.value)
self.weightPercentages.value = weightPercentages
weightLiftedTextField.value = modelLiftEvent.liftWeight.value
repetitionsTextField.value = "\(modelLiftEvent.repetitions)"
oneRepMaxTextField.value = modelLiftEvent.oneRepMax.value
}
我已经阅读了一堆关于对象生命周期的文档,但没有找到任何有用的信息。我希望当新的CalculatorLiftEventViewModelFromLiftEvent 被创建并分配给`CalculatorViewController' 的viewModel 属性时,它将替换对旧的引用并且它将不复存在。显然,这不是正在发生的事情。
有谁知道为什么当我从没有值(0.0 除外)的“计算器”视图(第 3 步)转到“设置”然后返回时,会显示之前的提升事件值?
【问题讨论】:
-
没有足够的代码来确定,但看起来
CalculatorLiftEventViewModelFromLiftEvent已注册为通知的观察者(通过调用subscribeToNotifications()提示)。如果为真,那将持有对保持其活动状态的对象的引用(并且,根据您的断点证据,继续向其发送通知以采取行动。) -
谢谢,@pauln。我很高兴添加更多您认为可能相关的代码。我查看了 Apple 关于基于选择器的观察 API 的文档,他们说从 iOS 9.0 开始,您不需要在其 dealloc 方法中取消注册观察者。他们接着说,“否则,您应该在观察者或传递给此方法的任何对象被释放之前调用 removeObserver(_:name:object:)。”不确定“否则”是什么意思。不然呢?
-
有趣的是,我今天刚刚在这里oleb.net/blog/2018/01/notificationcenter-removeobserver/… 读到了这个。基本上,尽管文档的一部分说不,但 Foundation 注释和 addObserver 文档都说是。所以现在你仍然需要这样做,反正也没有什么坏处。
-
“否则”是指目标在 iOS 9.0 和 macOS 10.11 之前的应用程序。尽管如此,这还是很可疑的,尝试注销可能会发现问题。另外,您的视图模型类型是对象还是结构?
-
@UpholderOfTruth - 哈!我今天早上也读到了。这实际上是我听起来好像我知道我在说什么的原因。我会尝试并报告。
标签: swift mvvm notifications