Шаблоны программирования на Swift: Observer

example-observer_proSwift_ru

Продолжаем изучать шаблоны программирования на Swift. Сегодня мы поговорим о шаблоне Observer .

Observer

Что такое паттерн Observer? Вот вы когда нибудь подписывались на газету? Вы подписываетесь, и каждый раз когда выходит новый номер газеты вы получаете ее к своему дому. Вы никуда не ходите, просто даете информацию про себя, и организация которая выпускает газету сама знает куда и какую газету отнесту. Второе название этого паттерна – Publish – Subscriber.

Как описывает этот паттерн наша любимая GoF книга – Observer определяет одно-ко-многим отношение между объектами, и если изменения происходят в объекте – все подписанные на него объекты тут же узнают про это изменение.

Идея проста, объект который мы называем Subject – дает возможность другим объектам, которые реализуют интерфейс Observer, подписываться и отписываться от изменений происходящик в Subject. Когда изменение происходит – всем заинетерсованным объектам высылается сообщение, что изменение произошло. В нашем случае – Subject – это издатель газеты, Observer это мы с вами – те кто подписывается на газету, ну и собсвтенно изменение – это выход новой газеты, а оповещение – отправка газеты всем кто подписался.

Когда используется паттерн:

  1. Когда Вам необходимо сообщить всем объектам подписанным на изменения, что изменение произошло, при этом вы не знаете типы этих объектов.
  2. Изменения в одном объекте, требуют чтобы состояние изменилось в других объектах, при чем количество объектов может быть разное.

Реализация этого паттерна возможно двумя способами:

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.


Метки:

1 Comment on “Шаблоны программирования на Swift: Observer

  1. в Swift5 крашится при попытки subj.addObserver(observer: oneSub)
    из за NSMutableSet(). Если заменять его на var observerCollection = [StandartObserver](). То все работает. Ну вот только с удалением объекта у нас проблемы.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

*

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.