swift - 为什么旧 View 模型会响应通知?

标签 swift mvvm notifications

我正在使用 MVVM 创建一个举重计算器应用程序 (Swift 4),并且已经尝试了 2 天来找出为什么本应死亡的 View 模型仍在响应 UserDefaults.defaultsDidChange事件通知。

enter image description here

  1. 我启动应用程序:
    • 启动时,在 AppDelegate 中,我创建一个新的 lift 事件对象,并使用它为 `CalculatorViewController' 初始化一个新的 CalculatorLiftEventViewModelFromLiftEvent:
  2. 我计算升力并保存
  3. 我点击 + 按钮创建新的电梯:
    • 这会导致创建一个新的空电梯事件对象
    • 这个新的 lift 事件对象用于初始化新的 CalculatorLiftEventViewModelFromLiftEvent 对象
    • 然后,这个新的 CalculatorLiftEventViewModelFromLiftEvent 会分配给 CalculatorViewController 的 viewModel 属性,替换应用启动时创建的属性
    • 计算器屏幕上的值已清零,准备输入新的电梯事件
  4. 我点击“设置”按钮转至“设置”,在其中更改与当前提升事件关联的公式。
  5. 新公式将保存为默认值,并且会触发 UserDefaults.defaultsDidChange 通知
  6. 这是我无法弄清楚的部分:原始 View 模型仍然存在,并且仍在监听 UserDefault 通知。当我关闭“设置”屏幕并返回“计算器” View 时,先前已清除的电梯事件的值现在会重新出现。

以下是点击计算器屏幕上的 +(新)按钮时会发生的情况:

@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()
}

更新:以下是 CalculatorLiftEventViewModelFromLiftEvent 中的 deinitaddObserver。请注意,我没有使用基于 block 的观察。

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 中,我在此处放置了一个断点,因为当 View 模型听到 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 除外)的计算器 View (步骤 3)进入设置然后返回时,会显示之前的提升事件值?

最佳答案

我修复了在清除计算器、更改默认公式并返回计算器屏幕后显示之前的 liftEvent 的问题。

CalculatorViewController 上,当点击 + 按钮时,我不是创建一个新的 viewModel 并将其分配给 viewModel 属性,而是要求我的 AppDelegate 创建一个新的 CalculatorViewControllerCalculatorLiftEventViewModelFromLiftEvent 通过使用 launchCalculatorViewController 方法在应用启动时执行此操作。

CalculatorViewController中的原始代码:

@objc fileprivate func onNewButtonTapped(_ sender: UIBarButtonItem) {

    let newLiftEvent = dataManager.createNewLiftEvent()
    viewModel = CalculatorLiftEventViewModelFromLiftEvent(withLiftEvent: newLiftEvent, dataManager: dataManager)

    self.percentagesTableView.reloadData()

    setupView()
}

现在 CalculatorViewController 中的新代码:

@objc fileprivate func onNewButtonTapped(_ sender: UIBarButtonItem) {

    (UIApplication.shared.delegate as? AppDelegate)?.launchCalculatorViewController()

}

AppDelegate中:

func launchCalculatorViewController() {
    self.window = UIWindow(frame: UIScreen.main.bounds)
    let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
    if let initialViewController: CalculatorViewController = mainStoryboard.instantiateInitialViewController() as? CalculatorViewController {

        self.window?.rootViewController = initialViewController

        let liftEvent = dataManager.createNewLiftEvent()
        let viewModel = CalculatorLiftEventViewModelFromLiftEvent(withLiftEvent: liftEvent, dataManager: dataManager)
        initialViewController.viewModel = viewModel
        initialViewController.dataManager = dataManager
        self.window?.makeKeyAndVisible()

    }
}

不幸的是,我确定 CalculatorLiftEventViewModelFromLiftEvent 对象永远不会被释放,这告诉我我有一个不会释放的强引用循环:

enter image description here

这肯定是另一个SO问题。

关于swift - 为什么旧 View 模型会响应通知?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48230283/

相关文章:

javascript - Knockout.js - 如何在 View 之间切换

java - 即使应用程序关闭,如何设置在特定时间段在Android应用程序中弹出通知?

swift - 按字母顺序重新排序 TableView

ios - UITableViewCell 没有扩展以适应 UILabel 的高度,我做错了什么?

ios - 在 Swift 中获取某一天的事件数

c# - 我需要什么来进一步限定 DataContext 以进行绑定(bind)?

flutter - Provider 和 setstate 哪个更贵?

swift - 在 getDeliveredNotifications(completionHandler :) method) 中读取推送通知负载详细信息

android - 创建通知时出现空指针异常

swift - 在 Swift 中将字符串转换为日期