Ответ 1
Важно, что вы используете что-то (в отличие от ничего), и что вы используете уникальный и для личного использования его.
Основная ошибка здесь происходит, когда у вас есть наблюдение в одном из ваших классов, а затем кто-то подклассифицирует ваш класс, и они добавляют еще одно наблюдение за тем же наблюдаемым объектом и одним и тем же ключом. Если ваша оригинальная реализация observeValueForKeyPath:...
проверила только keyPath
, или наблюдаемый object
, или даже оба, этого может быть недостаточно, чтобы знать, что это вызвало ваше наблюдение. Использование context
, значение которого уникально и конфиденциально для вас, позволяет вам быть более уверенным, что данный вызов observeValueForKeyPath:...
- это тот вызов, который вы ожидаете от него.
Это было бы важно, если бы вы зарегистрировались только для уведомлений didChange
, но подкласс регистрируется для одного и того же объекта и keyPath с опцией NSKeyValueObservingOptionPrior
. Если вы не отфильтровывали вызовы на observeValueForKeyPath:...
с помощью context
(или проверяя словарь изменений), ваш обработчик выполнял бы несколько раз, когда вы ожидали, что он будет выполняться один раз. Нетрудно представить, как это может вызвать проблемы.
Я использую шаблон:
static void * const MyClassKVOContext = (void*)&MyClassKVOContext;
Этот указатель укажет на свое местоположение, и это место уникально (никакая другая статическая или глобальная переменная не может иметь этот адрес, а также не может иметь какой-либо объект с кучей или стеком, который когда-либо имел этот адрес - это довольно сильный, хотя и признанный не абсолютная, гарантия), благодаря компоновщику. const
делает это так, чтобы компилятор предупредил нас, если мы когда-нибудь попытаемся написать код, который изменит значение указателя, и, наконец, static
делает его закрытым для этого файла, поэтому никто за пределами этого файла не может получить ссылку на него (опять же, чтобы избежать столкновения).
Один шаблон, который я бы специально предостерег от использования, - это тот, который появился в вопросе:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSString *action = (NSString*)context;
if([action isEqualToString:GMAP_ANNOTATION_SELECTED]) {
context
объявляется как void*
, что означает, что все гарантии, которые могут быть сделаны в отношении того, что это такое. Бросив его на NSString*
, вы открываете большую коробку потенциального вреда. Если у кого-то еще есть регистрация, которая не использует параметр NSString*
для параметра context
, этот подход сработает, когда вы передадите значение, отличное от объекта, до isEqualToString:
. Равенство указателя (или альтернатива intptr_t
или uintptr_t
равенство) являются единственными безопасными проверками, которые могут использоваться со значением context
.
Использование self
как a context
является общим подходом. Это лучше, чем ничего, но имеет гораздо более слабую уникальность и конфиденциальность, поскольку другие объекты (не говоря уже о подклассах) имеют доступ к значению self
и могут использовать его как context
(вызывающий неоднозначность), в отличие от подхода я предложенной выше.
Также помните, что это не просто подклассы, которые могут вызвать ловушки здесь; Хотя это, возможно, редкая модель, нет ничего, что мешало бы другому объекту регистрировать ваш объект для новых наблюдений KVO.
Для улучшения читаемости вы можете также обернуть это макросом препроцессора, например:
#define MyKVOContext(A) static void * const A = (void*)&A;