Swift MVC архитектура с наблюдателем модели - PullRequest
0 голосов
/ 23 мая 2018

Используя подход 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 для этого примера.

Ответы [ 2 ]

0 голосов
/ 23 мая 2018

Это больше демпинг-комментарий, чем полноценный ответ.Но длинная история KVO - это функция, которую вы должны использовать, а не NotificationCenter.Процесс связывания становится значительно более простым в Swift4

Что касается того, что такое KVO: см. здесь и здесь .Для некоторых примеров, которые ориентированы на MVVM, вы можете увидеть здесь и здесь .И не позволяй MVVM тебя убрать.Это просто MVC с привязками, которые вы пытаетесь сделать точно так же + перенести логику представления на другой уровень.

Простой пример KVO в Swift 4 будет выглядеть следующим образом:

@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

0 голосов
/ 23 мая 2018

Чтобы просто визуализировать картинку, вы должны осознавать тот факт, что вы наблюдаете только изменение name.Поэтому нет смысла обновлять все остальные свойства Person.Вы наблюдаете name изменение, и оно обновляется соответственно, не говоря уже о других.

Так что не идеальное предположение, что age и gender могли быть изменены в процессе изменения name.Сказав это, вы должны рассмотреть возможность наблюдения всех свойств одно за другим и привязывать действия по-разному и изменять только компонент пользовательского интерфейса, который сопоставлен с этим конкретным свойством.

Примерно так:

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
}
...