iOS Patterns. Observer

С приготовлениями к свадьбе писать становиться ну просто очень некогда:) А паттернов чтобы описать еще огого как много:)

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

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

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

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

1. Когда Вам необходимо нотифицировать все объекты что изменение произошло, при этом вы не знаете типы этих объектов.

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

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

1. Notification

Notificaiton — механизм использование фичи NotificationCenter самой операционной системы. Использование NSNotificationCenter позволяет объектам комуницировать, даже не зная друг про друга. Это очень удобно использовать когда у вас в паралельном потоке пришел push-notification, или же обновилась база, и вы хотите дать об этом знать активному на даный момент View.

Чтобы запостить такое сообщение стоит использовать конструкцию типа:

    NSNotification *broadCastMessage = [NSNotification notificationWithName:@"broadcastMessage" object:self];
    NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter postNotification:broadCastMessage];

Как видим мы создали объект типа NSNotification в котором мы указали имя нашего оповещения : «broadcastMessage», и собственно сообщили о нем через notificationCenter.

Чтобы подписаться на событие в объекте который заинтересован в изменении стоит использовать следующую конструкцию:

    NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(update:) name:@"broadcastMessage" object:nil];

Собственно из кода все более-менее понятно: мы подписываемся на событие, и вызывается метод который задан в своействе selector.

2. Стандартный метод.
Стандартный метод, это реализация этого паттерна тогда, когда Subject знает про всех подписчиков, но при этом не знает их типа. Давайте начнем с того, что создадим протоколы для Subject и Observer:

@protocol StandardObserver <NSObject>
-(void) valueChanged:(NSString *)valueName newValue:(NSString *) newValue;
@end

@protocol StandardSubject <NSObject>
-(void) addObserver:(id<StandardObserver>) observer;
-(void) removeObserver:(id<StandardObserver>) observer;
-(void) notifyObjects;
@end

Теперь, давайте создадим реализацию Subject:

@interface StandardSubjectImplementation : NSObject <StandardSubject>
{
    @private NSString *_valueName;
    @private NSString *_newValue;
}
@property (nonatomic, strong) NSMutableSet *observerCollection;
-(void)changeValue:(NSString *)valueName andValue:(NSString *) newValue;
@end

@implementation StandardSubjectImplementation

-(NSMutableSet *) observerCollection
{
    if (_observerCollection == nil)
        _observerCollection = [[NSMutableSet alloc] init];

    return _observerCollection;
}

-(void) addObserver:(id<StandardObserver>)observer
{
    [self.observerCollection addObject:observer];
}

-(void) removeObserver:(id<StandardObserver>)observer
{
    [self.observerCollection removeObject:observer];
}

-(void) notifyObjects
{
    for (id<StandardObserver> observer in self.observerCollection) {
        [observer valueChanged: _valueName newValue:_newValue];
    }
}

-(void)changeValue:(NSString *)valueName andValue:(NSString *) newValue
{
    _newValue = newValue;
    _valueName = valueName;
    [self notifyObjects];
}
@end

Ну и куда же без обсерверов:

@interface SomeSubscriber : NSObject <StandardObserver>
@end

@implementation SomeSubscriber
-(void) valueChanged:(NSString *)valueName newValue:(NSString *)newValue
{
    NSLog(@"And some subscriber tells: Hmm, value %@ changed to %@", valueName, newValue);

}
@end

//и второй подписчик:
@interface OtherSubscriber : NSObject <StandardObserver>

@end

@implementation OtherSubscriber

-(void) valueChanged:(NSString *)valueName newValue:(NSString *)newValue
{
    NSLog(@"And some another subscriber tells: Hmm, value %@ changed to %@", valueName, newValue);
}
@end

Собственно — все:) теперь демо-код:

    StandardSubjectImplementation *subj = [[StandardSubjectImplementation alloc] init];
    SomeSubscriber *someSubscriber = [[SomeSubscriber alloc] init];
    OtherSubscriber *otherSubscriber = [[OtherSubscriber alloc] init];

    [subj addObserver:someSubscriber];
    [subj addObserver: otherSubscriber];

    [subj changeValue:@"strange value" andValue:@"newValue"];

И естественно log:

2013-02-16 17:31:43.176 ObserverPattern[24332:c07] And some subscriber tells: Hmm, value strange value changed to newValue

2013-02-16 17:31:43.177 ObserverPattern[24332:c07] And some another subscriber tells: Hmm, value strange value changed to newValue

Код примера традиционно можно найти тут: https://github.com/dimko1/ios_patterns/tree/master/ObserverPattern

UPD 1: Без примера с KVO — топик не смотрится, потому вот она добавка:)

Одна из моих самых любимых особенностей Obj-C — это key-value coding. Про него очень клево описанно в официальной документации, но если объяснять на валенках — то это возможность изменять значения свойств объекта с помощью строчек — которые указывают именно само название свойства. Как пример такие две конструкции идентичны:

    kvoSubj.changeableProperty = @"new value";

    [kvoSubj setValue:@"new value" forKey:@"changeableProperty"];

Такая гибкость дает нам доступ к еще одной очень замечательной возможности, которая называется key-value observing. Опять же, все круто описанно в документации, но если объяснять на валенках:) то это возможность подписаться на изменение любого свойства, у любого объекта который KV compilant, любым объектом. На самом деле легче объяснить на примере.

Давайте создадим клас с одним свойством, которое мы будем менять:

@interface KVOSubject : NSObject
@property (nonatomic, strong) NSString *changeableProperty;
@end

@implementation KVOSubject
@end

И создадим объект который будет слушать изменение свойства changeableProperty:

@interface KVOObserver : NSObject
@end

@implementation KVOObserver
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    NSLog(@"KVO: Value changed;");
}
@end

Как видим, этот класс реализует только один метод: observeValueForKeyPath. Этот метод будет вызван когда поменяется свойство объекта за которым мы наблюдаем.

Теперь тест:

    KVOSubject *kvoSubj = [[KVOSubject alloc] init];
    KVOObserver *kvoObserver = [[KVOObserver alloc] init];

    [kvoSubj addObserver:kvoObserver forKeyPath:@"changeableProperty"
                 options:NSKeyValueObservingOptionNew context:nil];

    kvoSubj.changeableProperty = @"new value";

    [kvoSubj setValue:@"new value" forKey:@"changeableProperty"];

    //because kvoSubj will be deallocated after this functions ends we need to remove observer information.
    [kvoSubj removeObserver:kvoObserver forKeyPath:@"changeableProperty"];

Как видно из примера, мы для объекта за которым мы наблюдаем, выполняем функцию addObserver — где устанавливаем кто будет наблюлать за изменениями, за изменениями какого свойства мы будем наблюдать и остальные опции. Дальше меняем значение свойства, и так как мы все это проделываем на нажатие кнопки — в конце мы удаляем наблюдателя с нашего объекта, что бы память не текла.
Лог говорит сам за себя:

2013-02-17 11:41:58.051 ObserverPattern[26689:c07] KVO: Value changed;

2013-02-17 11:41:58.052 ObserverPattern[26689:c07] KVO: Value changed;

iOS Patterns. Observer: Один комментарий

  1. Уведомление: iOS设计模式:观察者 | 破船之家

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

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s