带模型观察器的Swift MVC架构

时间:2018-05-23 02:06:04

标签: swift model-view-controller nsnotificationcenter notificationcenter

使用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. 更新模型
  4. 更新模型时,通知观察者调用updateLabels函数来更新用户界面。
  5. 此方法需要最后设置person.name,否则必须按两次updatePerson按钮才能更新整个用户界面。由于我只观察一个属性,因此通知并不代表整个类。有没有更好的方法来观察Swift模型(类或结构)的变化?

    注意 - 我对此示例使用RxSwift不感兴趣。

2 个答案:

答案 0 :(得分:1)

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

至于KVO是什么:请参阅herehere。对于一些关注MVVM的示例,您可以看到herehere。并且不要让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

复制并粘贴

答案 1 :(得分:0)

要简单地显示图片,您应该意识到您只观察name更改。因此更新Person的所有其他属性没有意义。您正在观察name更改并相应更新,更不用说其他人了。

因此,agegender在更改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
}