
Не подумайте ничего дурного. Просто при слове 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.