CalculatorViewController

CalculatorViewController

我正在使用MVVM创建举重计算器应用程序(Swift 4),并且已经尝试了2天,以弄清楚为什么应该死亡的视图模型仍然响应UserDefaults.defaultsDidChange事件通知。

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


我启动该应用程序:


在启动时,在AppDelegate中,我创建了一个新的提升事件对象,并使用它来为CalculatorViewController初始化一个新的CalculatorLiftEventViewModelFromLiftEvent

我计算升力并保存
我点击+按钮创建一个新的提升:


这将导致创建一个新的空提升事件对象
此新的提升事件对象用于初始化新的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()
}


更新:这是deinitaddObserver中的CalculatorLiftEventViewModelFromLiftEvent。注意,我没有使用基于块的观察。

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步)转到“设置”,然后返回时,会显示以前的举升事件值?

最佳答案

我已经解决了在清除计算器,更改默认公式并返回到计算器屏幕之后显示先前的liftEvent的问题。

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

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对象永远不会被释放,这告诉我我有一个强大的参考周期,它不会放过:

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

那将是另一个SO问题。

08-17 18:00