【问题标题】:Swift - trigger method in subclass when superclass property changesSwift - 超类属性更改时子类中的触发方法
【发布时间】:2020-11-10 04:49:53
【问题描述】:

我有三个超类和两个子类,如下所示:

class BaseLookupViewController {
}

class RetrieveCompletedordersViewController: BaseLookupViewController {
}

class LoginViewController: BaseLookupViewController {
}

在 BaseLookupViewController 我有一个属性“deviceOnline”:

internal var deviceConnected = true

这是使用不同类的观察者设置的:

override public func viewDidLoad() {
    super.viewDidLoad()
    stackView.customPadding(Constants.stackViewPadding, after: submitButton)
    addConectivityObserver()
}


// MARK: - Configuration

private func addConectivityObserver() {
    NotificationCenter.default.addObserver(self, selector: #selector(connectivityChanged(notification:)), name: .connectivityStatusChanged, object: nil)
}

@objc private func connectivityChanged(notification: Notification) {
    if notification.object as? ConnectionService.ConnectionStatus == .connected {
        deviceConnected = true
    } else {
        deviceConnected = false
    }
}

设置布尔值后,我需要在每个子类中触发特定方法。例如,在 RetrieveCompletedordersViewController 我需要触发以下内容:

    if deviceConnected {
        self.tabBarItem.image = #imageLiteral(resourceName: "retrieve_completed")
        self.errorLabel?.isHidden = true
    } else {
        self.tabBarItem.image = #imageLiteral(resourceName: "retrieve_offline")
        self.errorLabel?.isHidden = false
    }

还有一个类似的方法,但显然在另一个子类中的不同属性上。我真的想避免向每个子类添加属性观察器,因为这似乎没有必要。最干净的方法似乎是以某种方式在“deviceConnected”的 willSet 方法中设置子类的属性,但因为这些类不是单例 BaseLookupViewController。在没有重复代码的情况下实现这一目标的最简洁方法是什么?

【问题讨论】:

    标签: swift subclass superclass observers


    【解决方案1】:

    好吧,因为要调用的方法是依赖于类型的,所以应该有一个对象负责执行所述调用,我能看到的最直接和合乎逻辑的方法是让它们自己管理,这意味着每个子类都应该处理当 deviceConnected 属性改变时调用任何需要调用的方法。

    您可以在每个子类上覆盖所述属性并在didSet 中添加方法调用代码,您不会失去超类的任何功能,因为它仍然是由其超类定义的存储属性,这意味着,如果您是要向上转换到超类,您仍然可以访问该属性,因为覆盖不会使原始声明无效。

    类似这样的:

    class RetrieveCompletedordersViewController: BaseLookupViewController {
    
        override var deviceConnected: Bool {
            didSet {
                if deviceConnected {
                    tabBarItem.image = #imageLiteral(resourceName: "retrieve_completed")
                    errorLabel?.isHidden = true
                } else {
                    tabBarItem.image = #imageLiteral(resourceName: "retrieve_offline")
                    errorLabel?.isHidden = false
                }
            }
        }
    
    }
    

    为了调用特定于子类的didSet,您必须在子类实例本身上设置属性值,如果您向上转型并在超类上设置一个值,它将不会被执行,这实际上就是您的身份通过直接在超类中调用#selector 来完成。为了解决这个问题,添加一个带有闭包的通知观察者,这会将子类捕获为 self 并正确执行didSet

    var notificationObservers: Array<NSObjectProtocol> = []
    
    override public func viewDidLoad() {
        super.viewDidLoad()
        stackView.customPadding(Constants.stackViewPadding, after: submitButton)
        notificationObservers.append(
                NotificationCenter.default.addObserver(
                    forName: .connectivityStatusChanged,
                    object: nil,
                    queue: .main,
                    using: { [weak self] (notification) in
                               if notification.object as? ConnectionService.ConnectionStatus == .connected {
                                   self?.deviceConnected = true
                               } else {
                                   self?.deviceConnected = false
               })
        )
    }
    

    记住在不再使用通知时总是从通知中心删除观察者,例如当对象被取消初始化时,我喜欢用数组来处理清理:

    deinit {
            // Perform cleanup of any Apple Notification Center notifications to which we may have registered
            notificationObservers.forEach { (observer) in
                NotificationCenter.default.removeObserver(observer)
            }
        }
    

    【讨论】:

    • 感谢您对此的回复,我绝对喜欢这种方法。但是,当在超类中设置属性时,我的覆盖 var didSet 没有被奇怪地调用:/
    • 对不起,我错了,通过使用不捕获自我的选择器,检查更新的答案并尝试一下 :)
    • 嗯,奇怪的一个,仍然没有击中它。我要去玩一玩,看看能不能解决。非常感谢您的意见
    猜你喜欢
    • 2017-04-18
    • 2018-11-17
    • 2021-03-13
    • 2023-01-24
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多