Не подумайте ничего дурного. Просто при слове observer я вспоминаю лучшую стратегию всех времен Star Craft. Вы за кого играли или может быть играете? Я Зерга любил. А observer был у Протоссов. Ладно, перейдем к делу.
Что такое Key-Value Observing (KVO) в Objective-C?
Если мы заглянем на страницу официальной документации Apple, то обнаружим там следующее.
Key-value observing предоставляет механизм, который позволяет одним объектам быть оповещенными об изменении отдельных свойств других объектов. Это особенно применимо для коммуникации между моделью и контроллерами в приложении. В то же время модель может наблюдать за объектами другой модели или за другими объектами самой себя.
«Короче Склифософский!» Key-Value Observing?
Разберем KVO на простом примере. Предположим перед вами стоит задача прикрутить следующую фичу к приложению. Нужно добавить кнопку в виде смайлика, по нажатию на которую будет открываться NSPopover. Смайлик при этом должен менять вид. NSPopver закрывается при нажатии в любом месте экрана. Смайлик должен вернуться в первоначальное состояние.
Задача, в принципе, очень простая.
При нажатии на кнопку показываем NSPopover и вызываем метод, который обновляет состояние кнопки, в котором, собственно и меняем смайлик.
- (IBAction)showFeedbackPopover:(id)sender { [m_pFeedbackPopover showRelativeToRect:[sender bounds] ofView:sender preferredEdge:NSMinXEdge]; [self updateFeedbackButtonWithPressedState:YES]; } - (void)updateFeedbackButtonWithPressedState:(BOOL)pressed { if (pressed) { [m_pFeedbackButton setImage:[NSImage imageNamed:@"Smile_hover"]]; [m_pFeedbackButton setAlternateImage:[NSImage imageNamed:@"Smile"]]; } else { [m_pFeedbackButton setImage:[NSImage imageNamed:@"Smile"]]; [m_pFeedbackButton setAlternateImage:[NSImage imageNamed:@"Smile_hover"]]; } }
Вопрос в том, как вернуть смайлик в первоначальное состояние, когда NSPopover закроется?
И тут нам поможет Key-Value Observing.
Если мы заглянем в класс NSPopver, то обнаружим в нем свойство shown, которое возвращает YES, когда NSPopover показывается, и NO — когда спрятан.
/* YES if the popover is being shown, NO otherwise. */ @property(readonly, getter=isShown) BOOL shown;
Для того, чтобы нам решить нашу задачу, нам нужно как-то следить за этим свойством shown и менять смайлик, когда оно изменяется. Это и делает KVO.
Первое, что нам надо сделать, добавить обсервер. Обычно это можно сделать в методах viewDidLoad или awakeFromNib
[m_pFeedbackPopover addObserver:self forKeyPath:@"shown" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
Мы только что добавили обсервер к нашему экземпляру класса NSPopover, который будет следить за изменениями свойства shown. В опциях мы указали отслеживать новое значение и старое.
Теперь нам нужно реализовать метод, куда, собственно, будут приходить все эти изменения. Этот метод называется
— (void) observeValueForKeyPath: ofObject: change: context:
и является методом класса NSKeyValueObserving фреймворка Foundation.
#pragma mark - KVO Observing - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { NSLog(@"\nobserveValueForKeyPath: %@\nofObject: %@\nchange: %@", keyPath, object, change); }
Запустим наше приложение и нажмем на смайлик 1 раз для открытия диалога. Теперь нажмем в любом месте экрана для закрытия диалога. NSLog нам вывел следующее сообщение.
2016-03-22 16:42:40.589 Annotator[52163:1197836] observeValueForKeyPath: shown ofObject: <NSPopover: 0x600000123c00> change: { kind = 1; new = 1; old = 0; } 2016-03-22 16:42:42.975 Annotator[52163:1197836] observeValueForKeyPath: shown ofObject: <NSPopover: 0x600000123c00> change: { kind = 1; new = 0; old = 1; }
Как мы можем видеть нам пришел NSDictionary *change с ключами kind, new и old. И как мы можем видеть в первом случае новое значение shown стало 1, старое 0. Когда мы закрыли окно — shown изменился в первоначальное состояние.
Вот и все. Нам остается преобразовать значение ключей в BOOL (т.к. ключи содержат NSString* как можно видеть в описании метода) и вызвать метод, обновляющий состояние нашей кнопки.
#pragma mark - KVO Observing - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { NSString *kChangeNew = [change valueForKey:@"new"]; BOOL shown = kChangeNew.boolValue; [self updateFeedbackButtonWithPressedState:shown]; }
В этом и есть Key-Value Observing.