Наблюдение за значениями по ключам (или сокращенно KVO) — это механизм, позволяющий одному объекту быть зарегистрированным другим объектом, чтобы автоматически уведомлять об изменении значения во втором объекте.

Для того чтобы выполнить действие регистрации, требуется, чтобы в какой-то момент стал видимым, прежде всего, второй объект или чтобы оба эти объекта были видимы третьему объекту. Впоследствии второй объект может оказаться в состоянии посылать сообщения первому объекту, не прибегая к переменной экземпляра для ссылки на первый объект и даже ничего не зная о классе первого объекта или открыто объявленных методах. В архитектурном отношении этот механизм похож на пары “цель-действие” (см. главу 11), но он подходит для любых двух объектов. (Механизм KVO предоставляется через неформальный протокол NSKeyValueObs%rving, который фактически представляет собой ряд категорий в классе NSObject и других классах.) Второй объект совсем не обязательно должен быть экземпляром вашего собственного класса. Это может быть экземпляр встроенного класса Cocoa. Первый объект должен содержать ваш собственный код, чтобы вовремя реагировать на уведомления.

 

 

Предупреждение для программистов в системе OS X

Характерные для системы OS X привязки отсутствуют в системе iOS, но -------' иногда для достижения аналогичных целей можно воспользоваться механизмом KVO.

 

 

Механизм KVO можно разделить на три категории.

Регистрация

Для того чтобы отреагировать на изменение значения, принадлежащего объекту А, объект В должен быть зарегистрирован объектом А.

Изменение

Изменение значения, принадлежащего объекту А, должно происходить особым образом, совместимым с механизмом KVO. Как правило, это означает применение метода доступа, совместимого с механизмом доступа к значениям по ключам, для внесения изменений. (Если объект А относится к вашему собственному классу, вы можете дополнительно написать код, который будет вручную вызывать другие виды изменений, которые считаются совместимыми с механизмом KVO.)

Уведомление

Объект В автоматически уведомляется, что значение в объекте А изменилось и что он может теперь отреагировать на него так, как посчитает нужным.

Ниже приведен довольно простой пример, но он в достаточной степени демонстрирует действие механизма KVO. В этом примере имеется класс MyClassl, которому принадлежит объект objectA, а также класс MyClass2, которому принадлежит объект objectB. Получив ссылки на эти объекты, мы регистрируем объект objectB для реагирования на изменения в переменной экземпляра value объекта objectA, а затем изменяем значение в этой переменной. Безусловно, объект objectB автоматически уведомляется обо всех изменениях.

// MyClassl.h:

Sinterface MyClassl : NSObject

^property (nonatomic, copy) NSString* value;

Send

// MyClass2.m:

- (void) observeValueForKeyPath:(NSString *)keyPath

ofObject:(id)object

change:(NSDictionary *)change context:(void *)context (

NSLog(@"I heard about the change!");

II где-то еще объекты определяются полностью:

MyClassl* objectA * [MyClassl new];

MyClass2* objectB - [MyClass2 new];

II зарегистрировать для механизма KVO

[objectA addObserver:objectB forKeyPath:@"value” options:0 context:nil]; О // change the value in a KVO compliant way objectA.value = @"Hello, world!"; в

// в результате вызывается метод observeValueForKeyPath:...

II для объекта objectB ®

  • Для регистрации объекта objectB с целью реагировать на изменения в значении объекта objectA вызывается метод addObserver: forKeyPath:. Его параметры options: и context:, рассматриваемые далее, не используются. (В частности, параметр context: служит для передачи значения, которое должно возвратиться как часть уведомления; см. главу 12.)

На практике объект В должен был бы, скорее всего, зарегистрироваться, чтобы реагировать на изменения в пути к ключу объекта А. В данном примере это организовано искусственно для максимального упрощения кода.

  • Значение объекта objectA изменяется совместимым с механизмом KVO способом, а именно: передачей этого значения через метод установки, поскольку установка значения свойства равнозначна его передаче через метод установки. Это еще одна причина для применения методов доступа (и свойств), так как они помогают обеспечить совместимость с механизмом KVO при изменении значения.
  • При изменении значения объекта objectA автоматически наступает третья стадия, когда вызывается метод observeValueForKeyPath:.... для объекта objectB. Этот метод реализован в классе MyClass2, чтобы получить уведомление. В данном простом примере предполагается получить только одно уведомление, и поэтому далее происходит регистрация того, что оно действительно получено. На практике один объект может быть зарегистрирован для получения более чем одного уведомления в соответствии с механизмом KVO. Для различения разных уведомлений и принятия соответствующих решений используются входящие параметры.

При вызове метода observeValueForKeyPath:... требуется хотя бы знать, что собой представляет новое значение. Это нетрудно выяснить, поскольку объекту, в котором произошли изменения, была передана ссылка наряду с путем к ключу для доступа к значению в этом объекте. Следовательно, с помощью механизма KVC можно сделать запрос объекта, в котором произошли изменения, в самом общем виде, как показано ниже.

- (void) observeValueForKeyPath:(NSString *)keyPath

ofObject:(id)object

change:(NSDictionary *)change

context:(void *)context { id newValue - [object valueForKeyPath:keyPath];

NSLog(@"The key path %0 changed to          keyPath, newValue);

}

Можно также сделать запрос, чтобы включить новое значение в уведомление. Это зависит от аргумента options:, передаваемого при первоначальной регистрации. В приведенном ниже фрагменте кода делается запрос на включение как старого, так и нового значения в уведомление.

objectA.value = @"Hello";

[objectA addObserver:objectB forKeyPath:@"value"

options: NSKeyValueObservingOptionNew I NSKeyValueObservingOptionOld context: nil];

objectA.value = @"Goodbye"; // инициируется уведомление

При получении уведомления старое и новое значения извлекаются из словаря change следующим образом:

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object

change:(NSDictionary *)change context:(void *)context ( id newValue = change[NSKeyValueChangeNewKey]; id oldValue = change[NSKeyValueChangeOldKey];

NSLog(@"The key path %@ changed from %@ to keyPath, oldValue, newValue);

}

В процессе регистрации никакого управления памятью не происходит, поэтому именно на вас возлагается обязанность снять объект В с регистрации, прежде чем он будет уничтожен. В противном случае объект А может впоследствии попытаться отправить уведомление по висячему указателю (см. главу 12). Для того чтобы снять объект А с регистрации, ему посылается сообщение removeObserver: f orKeyPath:. Наблюдатель должен быть снят с регистрации явным образом по каждому пути к ключу, по которому он зарегистрирован. В качестве второго аргумента посылаемого метода нельзя использовать пустое значение (nil), означающее все пути к ключам. На практике там, где объект В, вероятно, зарегистрировался с помощью объекта А, он должен быть снят с регистрации с помощью того же самого объекта А. Это, возможно, должно быть сделано в его реализации метода dealloc. Следует, однако, иметь в виду, что для этого у объекта В должна быть ссылка на объект А.

Кроме того, объект В должен сняться с регистрации на получение уведомлений от объекта А, если последний собирается прекратить свое существование! Данное требование кажется обременительным, а его причины не совсем ясны. По-видимому, это требование связано с управлением памятью, которое происходит в механизме KVO и обеспечивает его работоспособность. Правда, среда выполнения аккуратно предупредит в журнале регистрации, если объект, наблюдаемый с помощью механизма KVO, собирается прекратить свое существование. (Однако вы не получите такое предупреждение, если объект наблюдателя прекращает свое существование. Об этом вы узнаете лишь после того, как сообщение, отправленное висячему указателю, приведет к аварийному завершению вашего приложения.)

Начинающим программировать в операционной системе iOS нередко оказывается непонятно, как пользоваться механизмом KVO для наблюдения за изменениями, происходящими в изменяемом массиве, чтобы вовремя получить уведомление о вводе, удалении или замене объекта в этом массиве. Ввести наблюдатель в массив нельзя, и поэтому наблюдение за изменениями в нем можно организовать через объект, у которого имеется путь к ключу для данного

массива, используя, например, методы доступа. В таком случае простейшее решение состоит в том, чтобы получить доступ к массиву с помощью метода mutableArrayValueForKey:, который предоставляет наблюдаемый объект-заместитель. В приведенном ниже примере кода устанавливается объект со свойством theData в виде массива словарей (см. главу 12).

(

{

description - "The one with glasses."; name - Manny;

>,

{

description - "Looks a little like Governor Dewey."; name Мое;

},

{

description - "The one without a mustache."; name “ Jack;

}

)

Допустим, что это массив типа NSMutableArray. В таком случае рассматриваемый здесь объект можно зарегистрировать для наблюдения за путем к ключу @"theData" следующим образом:

 

[objectA addObserver:objectB forKeyPath:@"theData" options:0 context:nil];

 

Теперь объект В будет получать уведомления об изменениях, происходящих в данном изменяемом массиве, но только если эти изменения делаются с помощью метода mutableArrayValueForKey: для объекта-заместителя, как показано ниже.

[[objectA mutableArrayValueForKeyPath:@"theData"] removeObjectAtlndex:0];

// уведомление инициировано

По-видимому, было бы обременительно требовать от клиентов вызывать метод mutableArrayValueForKey: самостоятельно. В качестве простого решения объект А должен сам предоставить метод получения, вызывающий метод mutableArrayValueForKey:. Ниже приведена одна из возможных реализаций такого решения.

II MyClassl.h:

Ointerface MyClassl : NSObject

Sproperty (nonatomic, strong, getter-theDataGetter) NSMutableArray* theData;

@end

II MyClassl.m:

- (NSMutableArray*) theDataGetter (

return [self mutableArrayValueForKey:@"theData"];

)

В итоге любому клиенту становится известно, что у данного объекта имеется ключ @"theData" и свойство theData и что по этому ключу можно зарегистрироваться для наблюдения и затем получить доступ к изменяемому массиву через свойства theData, как показано ниже.

[objectA addObserver:objectB forKeyPath:@"theData"

options: NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

[objectA.theData removeObjectAtlndex:0]; II уведомление инициировано

Если вы собираетесь принять именно такой подход, то должны также реализовать (в классе MyClassl) четыре метода, совместимых с механизмом KVC, в качестве фасада изменяемого массива (см. главу 12), хотя можно, по-видимому, обойтись и без них. Несмотря на то что эти методы кажутся тривиальными, поскольку они лишь делегируют переменной экземпляра self->_theData эквивалентные вызовы, тем не менее они будут вызываться поставляемым объектом-заметителем ради повышения его эффективности (а некоторые считают, что и надежности). В отсутствие этих методов объект-заместитель прибегает к непосредственной установке переменной экземпляра, заменяя весь изменяемый массив всякий раз, когда клиент вносит изменения в этот массив:

- (NSUInteger) countOfTheData { return [self->_theData count];

}

- (id) objectlnTheDataAtlndex: (NSUInteger) ix {

return self->_theData[ix];

}

- (void) insertObject: (id) val inTheDataAtlndex: (NSUInteger) ix {

[self->_theData insertObject:val atlndexsix];

}

- (void) removeObjectFromTheDataAtlndex:               (NSUInteger) ix {

[self->_theData removeObjectAtlndex: ix];

}

Дело усложняется, если требуется наблюдать за изменениями в отдельном элементе массива. Допустим, имеется изменяемый массив словарей. Для наблюдения за изменениями значения ключа 0"description" любого словаря в массиве потребуется зарегистрироваться по данному ключу для каждого массива словарей в отдельности. Это можно сделать эффективно с помощью метода экземпляра addObserver: toObjectsAtlndexes : f orKeyPath: opti ons : context:, но если сам массив является изменяемым, то придется зарегистрироваться по данному ключу для каждого нового словаря, последовательно вводимого в массив, а также сниматься с регистрации, когда словарь удаляется из массива, что кажется слишком обременительным.

Свойства встроенных классов, предоставляемых компанией Apple, как правило, совместимы с механизмом KVO. На самом деле это относится ко многим классам, в которых свойства, по существу, не применяются. Например, класс NSUserDefaults совместим с механизмом KVO. К сожалению, компания Apple предупреждает, что рассчитывать на недокументированную совместимость с механизмом KVO не следует.

С другой стороны, в некоторых классах Cocoa явно приветствуется применение механизма KVO. Они трактуют его как основной механизм уведомлений вместо центра уведомлений и класса NSNotif ication. Например, класс AVPlayer, отвечающий за воспроизведение мультимедийного содержимого, включая фильмы, обладает различными свойствами, в том числе status и rate, которые служат для уведомления о готовности мультимедийных средств к воспроизведению или же о факте воспроизведения ими мультимедийного содержимого. Как утверждается в документации AV Foundation Programming Guide, предоставляемой компанией Apple, для контроля над воспроизведением мультимедийного содержимого средствами класса AVPlayer следует применять к его свойствам механизм наблюдения за значениями по ключам.

Механизм наблюдения за значениями по ключам довольно основателен, и поэтому за полным его описанием обращайтесь к документации Key-Value Observing Guide, предоставляемой компанией Apple. Ему присущи и некоторые серьезные недостатки. Например, все уведомления, к сожалению, поступают в результате вызова одного и того же метода observeValueForKeyPath:. .., становящегося из-за этого критическим элементом с точки зрения производительности. Следить за тем, кто за кем наблюдает, обеспечивать надлежащие срок действия наблюдателя и наблюдаемого и снимать наблюдатель с регистрации, прежде чем он прекратит свое существование, будет не так-то просто. Однако, как правило, механизм KVO приносит пользу, когда требуется поддерживать согласованность значений в разных объектах.


 

 

 

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