Шаблоны программирования на Swift: Observer
Продолжаем изучать шаблоны программирования на Swift. Сегодня мы поговорим о шаблоне Observer .
Observer
Что такое паттерн Observer? Вот вы когда нибудь подписывались на газету? Вы подписываетесь, и каждый раз когда выходит новый номер газеты вы получаете ее к своему дому. Вы никуда не ходите, просто даете информацию про себя, и организация которая выпускает газету сама знает куда и какую газету отнесту. Второе название этого паттерна – Publish – Subscriber.
Как описывает этот паттерн наша любимая GoF книга – Observer определяет одно-ко-многим отношение между объектами, и если изменения происходят в объекте – все подписанные на него объекты тут же узнают про это изменение.
Идея проста, объект который мы называем Subject – дает возможность другим объектам, которые реализуют интерфейс Observer, подписываться и отписываться от изменений происходящик в Subject. Когда изменение происходит – всем заинетерсованным объектам высылается сообщение, что изменение произошло. В нашем случае – Subject – это издатель газеты, Observer это мы с вами – те кто подписывается на газету, ну и собсвтенно изменение – это выход новой газеты, а оповещение – отправка газеты всем кто подписался.
Когда используется паттерн:
- Когда Вам необходимо сообщить всем объектам подписанным на изменения, что изменение произошло, при этом вы не знаете типы этих объектов.
- Изменения в одном объекте, требуют чтобы состояние изменилось в других объектах, при чем количество объектов может быть разное.
Реализация этого паттерна возможно двумя способами:
1. Notification
Notificaiton – механизм использования возможностей NotificationCenter самой операционной системы. Использование NSNotificationCenter позволяет объектам комуницировать, даже не зная друг про друга. Это очень удобно использовать когда у вас в паралельном потоке пришел push-notification, или же обновилась база, и вы хотите дать об этом знать активному на даный момент View.
Чтобы послать такое сообщение стоит использовать конструкцию типа:
// // proSwift.ru // let broadCastMessage = NSNotification(name: "broadCastMessage", object: nil) let notificationCenter = NSNotificationCenter.defaultCenter() notificationCenter.addObserver(self, selector: #selector(update), name: "bradCastMessage", object: nil)
Как видим мы создали объект типа NSNotification в котором мы указали имя нашего оповещения: “broadcastMessage”, и собственно сообщили о нем через NotificationCenter.Из кода все более-менее понятно: мы подписываемся на событие, и вызывается метод который задан в свойстве selector.
2. Стандартный метод.
Стандартный метод, это реализация этого паттерна тогда, когда Subject знает про всех подписчиков, но при этом не знает их типа. Давайте начнем с того, что создадим протоколы для Subject и Observer:
// // proSwift.ru // protocol StandartObserver: class { func valueChanged(valueName: String, newValue: String) } protocol StandartSubject: class { func addObserver(observer: StandartObserver) func removeObserver(observer: StandartObserver) func notifyObject() }
Теперь, давайте создадим реализацию Subject:
// // proSwift.ru // class StandartSubjectImplementation: StandartSubject { private var valueName: String? private var newValue: String? var observerCollection = NSMutableSet() func addObserver(observer: StandartObserver) { self.observerCollection.addObject(observer) } func removeObserver(observer: StandartObserver) { self.observerCollection.removeObject(observer) } func notifyObject() { for observer in observerCollection { (observer as! StandartObserver).valueChanged(self.valueName!, newValue: self.newValue!) } } func changeValue(valueName:String, andValue newValue:String) { self.newValue = newValue self.valueName = valueName notifyObject() } }
Ну и куда же без обсерверов:
// // proSwift.ru // class SomeSubscriber: StandartObserver { func valueChanged(valueName: String, newValue: String) { print("Первый обозреватель говорит: Значение \(valueName) поменялось на \(newValue)") } } class OtherSubscriber: StandartObserver { func valueChanged(valueName: String, newValue: String) { print("И второй обозреватель говорит: Значение \(valueName) поменялось на \(newValue)") } }
Собственно – все:) теперь демо-код:
// // proSwift.ru // let subj = StandartSubjectImplementation() let oneSub = SomeSubscriber() let twoSub = OtherSubscriber() subj.addObserver(oneSub) subj.addObserver(twoSub) subj.changeValue("Важное значение", andValue: "09 целых и триста пятьдесять восемь тысячных")
И естественно log:
Первый обозреватель говорит: Значение Важное значение поменялось на 09 целых и триста пятьдесять восемь тысячных И второй обозреватель говорит: Значение Важное значение поменялось на 09 целых и триста пятьдесять восемь тысячных
Пример GitHub:
Паттерн наблюдатель используется, чтобы позволить объекту опубликовать изменения в своем состоянии. Другие объекты, подписанные на исходный объект, должны быть немедленно уведомлены о любых изменениях в исходном объекте.
// // proSwift.ru // // https://github.com/ochococo/Design-Patterns-In-Swift/blob/master/source/behavioral/observer.swift protocol PropertyObserver : class { func willChangePropertyName(propertyName:String, newPropertyValue:AnyObject?) func didChangePropertyName(propertyName:String, oldPropertyValue:AnyObject?) } class TestChambers { weak var observer:PropertyObserver? var testChamberNumber: Int = 0 { willSet(newValue) { observer?.willChangePropertyName("testChamberNumber", newPropertyValue:newValue) } didSet { observer?.didChangePropertyName("testChamberNumber", oldPropertyValue:oldValue) } } } class Observer : PropertyObserver { func willChangePropertyName(propertyName: String, newPropertyValue: AnyObject?) { if newPropertyValue as? Int == 1 { print("Okay. Look. We both said a lot of things that you're going to regret.") } } func didChangePropertyName(propertyName: String, oldPropertyValue: AnyObject?) { if oldPropertyValue as? Int == 0 { print("Sorry about the mess. I've really let the place go since you killed me.") } } } var observerInstance = Observer() // создаем экземпляр Обозревателя var testChambers = TestChambers() // создаем экземпляр объекта для теста testChambers.observer = observerInstance // устанавливаем наблюдателя в свойство тестового объекта testChambers.testChamberNumber += 1 // изменяем наблюдаемое свосйтво //Вот лог в консоль: // Okay. Look. We both said a lot of things that you're going to regret. // Sorry about the mess. I've really let the place go since you killed me.
Данный код давайет разберем по частям. Для начала — протокол. Ну тут ничего сложного. Далее следует класс TestChambers у которого свойство принимает объект класса, который соответсвует вышеописанному протоколу. Также мы используем наблюдатели свойств, о которых мы говорил в статье Наблюдатели свойств в Swift: willSet и didSet. Далее мы декларируем класс обозревателя, который соответсвует протоколу, методы которого будут вызываться при изменении свойства.
Ну и после запуска тестового кода мы видим, что сначала срабатывает наблюдатель ДО изменения свойства willSet, а ПОСЛЕ изменения значения свойства didSet.
P.S.
Как по мне, тема NSNotification не раскрыта. В скором времени я планирую опубликовать пример работы NSNotificationCenter.
—
в Swift5 крашится при попытки subj.addObserver(observer: oneSub)
из за NSMutableSet(). Если заменять его на var observerCollection = [StandartObserver](). То все работает. Ну вот только с удалением объекта у нас проблемы.