【问题标题】:Swift MVC architecture with model observer带有模型观察器的 Swift MVC 架构
【发布时间】:2018-05-23 02:06:04
【问题描述】:

使用 MVC 方法进行 iOS 应用程序开发,我想通过发布到 NotificationCenter 来观察模型的变化。对于我的示例,Person.swift 模型是:

class Person {

    static let nameDidChange = Notification.Name("nameDidChange")

    var name: String {
        didSet {
            NotificationCenter.default.post(name: Person.nameDidChange, object: self)
        }
    }

    var age: Int
    var gender: String

    init(name: String, age: Int, gender: String) {
        self.name = name
        self.age = age
        self.gender = gender
    }
}

观察模型的视图控制器如下图所示:

class ViewController: UIViewController {

    let person = Person(name: "Homer", age: 44, gender: "male")

    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var ageLabel: UILabel!
    @IBOutlet weak var genderLabel: UILabel!
    @IBOutlet weak var nameField: UITextField!
    @IBOutlet weak var ageField: UITextField!
    @IBOutlet weak var genderField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        nameLabel.text = person.name
        ageLabel.text = String(person.age)
        genderLabel.text = person.gender

        NotificationCenter.default.addObserver(self,
                                               selector: #selector(self.updateLabels),
                                               name: Person.nameDidChange, object: nil)
    }

    @IBAction func updatePerson(_ sender: Any) {
        guard let name = nameField.text, let age = ageField.text, let gender = genderField.text else { return }
        guard let ageNumber = Int(age) else { return }
        person.name = name
        person.age = ageNumber
        person.gender = gender
    }

    @objc func updateLabels() {
        nameLabel.text = person.name
        ageLabel.text = String(person.age)
        genderLabel.text = person.gender
    }
}

示例应用的工作原理如下:

  1. 在文本字段中输入姓名、年龄和性别
  2. 按updatePerson 按钮从文本字段值更新模型
  3. 模型更新时,通知观察者调用updateLabels函数更新用户界面。

此方法需要最后设置person.name,否则必须按两次updatePerson 按钮才能更新整个用户界面。而且由于我只观察一个属性,因此通知并不代表整个班级。有没有更好的方法来观察 Swift 中模型(类或结构)的变化?

注意 - 我对在这个例子中使用 RxSwift 不感兴趣。

【问题讨论】:

    标签: swift model-view-controller nsnotificationcenter notificationcenter


    【解决方案1】:

    这更像是一个倾销评论,而不是一个令人满意的答案。但长话短说 KVO 是您应该使用的功能,而不是 NotificationCenter。绑定过程在 Swift4 中变得更加简单

    关于 KVO 是什么:请参阅 here 和 here。对于一些以 MVVM 为重点的示例,您可以查看 here 和 here。并且不要让 MVVM 左右你。它只是带有绑定的 MVC,您尝试做完全相同的事情 + 将表示逻辑移动到不同的层。

    Swift 4 中的一个简单 KVO 示例如下所示:

    @objcMembers class Foo: NSObject {
        dynamic var string: String
        override init() {
            string = "hotdog"
            super.init()
        }
    }
    
    let foo = Foo()
    
    // Here it is, kvo in 2 lines of code!
    let observation = foo.observe(\.string) { (foo, change) in
        print("new foo.string: \(foo.string)")
    }
    
    foo.string = "not hotdog"
    // new foo.string: not hotdog
    

    您还可以创建自己的Observable 类型,如下所示:

    class Observable<ObservedType>{
    
        private var _value: ObservedType?
    
        init(value: ObservedType) {
            _value = value
        }
    
        var valueChanged : ((ObservedType?) -> ())?
    
    
        public var value: ObservedType? {
            get{
                return _value // The trick is that the public value is reading from the private value...
            }
    
            set{
                _value = newValue
                valueChanged?(_value)
            }
        }
    
        func bindingChanged(to newValue : ObservedType){
            _value = newValue 
            print("value is now \(newValue)")
        }
    }
    

    然后创建一个可观察的属性:

    class User {
        //    var name : String <-- you normally do this, but not when you're creating as such
        var name : Observable<String>
    
        init (name: Observable<String>){
            self.name = name            
        }
    }
    

    上面的类(Observable)是从Swift Designs patterns book复制粘贴过来的

    【讨论】:

    • 这与使用NotificationCenter 观察者有何不同?
    • 使用 KVO,您不必使用didSet,也不会遇到您刚才提到的问题。简单地说,您只需观察任何类的任何属性/键路径而无需任何设置在您要观察的类/属性。而且从 Swift4 开始,它变得更加方便。这意味着与 NotificationCenter 不同,您不必发布...然后添加观察者...然后有一个选择器...您只需像 this post 一样简单的单个回调
    • 与您在答案中给出的示例相比,您最后评论中链接中的代码示例更加直接。请使用示例here 中概述的方法修改您的答案。
    【解决方案2】:

    为了简单地可视化图片,您应该意识到您只观察到name 的变化。所以更新Person 的所有其他属性是没有意义的。您正在观察 name 的变化,并且它正在相应地更新,更不用说其他人了。

    因此,age 和gender 可能在更改name 的过程中发生了更改,这不是一个理想的假设。话虽如此,您应该考虑一一观察所有属性并以不同方式绑定操作,并仅修改映射到该特定属性的 UI 组件。

    类似这样的:

    override func viewDidLoad() {
        ...
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(self.updateName),
                                               name: Person.nameDidChange, object: nil)
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(self.updateAge),
                                               name: Person.ageDidChange, object: nil)
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(self.updateGender),
                                               name: Person.genderDidChange, object: nil)
        ...
    }
    
    @objc func updateName() {
        nameLabel.text = person.name
    }
    @objc func updateAge() {
        ageLabel.text = String(person.age)
    }
    @objc func updateGender() {
        genderLabel.text = person.gender
    }
    

    【讨论】:

    • 我想避免按照您的建议将观察者应用于每个属性。这在可能有更多属性的更大应用中是不可行的。为了这个问题,我让我的示例保持简单,但实际上应用程序要复杂得多。
    猜你喜欢
    • 2014-05-15
    • 2016-08-26
    • 1970-01-01
    • 2012-07-09
    • 2018-09-16
    • 2012-09-02
    • 2011-12-21
    • 1970-01-01
    • 2015-09-07
    相关资源
    最近更新 更多