Подтвердить что ты не робот

Рекомендации по контекстному параметру в addObserver (KVO)

Мне было интересно, что вы должны установить указатель Context в KVO, когда вы наблюдаете свойство. Я только начинаю использовать KVO, и я не слишком разбирался в документации. Я вижу на этой странице: http://www.jakeri.net/2009/12/custom-callout-bubble-in-mkmapview-final-solution/ автор делает это:

[annView addObserver:self
forKeyPath:@"selected"
options:NSKeyValueObservingOptionNew
context:GMAP_ANNOTATION_SELECTED];

И затем в обратном вызове делает следующее:

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context{

NSString *action = (NSString*)context;


if([action isEqualToString:GMAP_ANNOTATION_SELECTED]){

Я предполагаю, что в этом сценарии автор просто создает строку, которая будет идентифицирована позже в обратном вызове.

Затем в iOS 5 Нажимая книгу "Лимиты", я вижу, что он делает это:

[self.target addObserf:self forKeyPath:self.property options:0 context:(__bridge void *)self];

обратный вызов:

if ((__bridge id)context == self) {
}
else {
   [super observeValueForKeyPath .......];
}

Мне было интересно, есть ли стандартная или лучшая практика для перехода в контекстный указатель? Спасибо!

4b9b3361

Ответ 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;

Ответ 2

Контекст KVO должен быть указателем на статическую переменную, как демонстрирует этот gist. Как правило, я считаю, что делаю следующее:

В верхней части моего файла ClassName.m У меня будет строка

static char ClassNameKVOContext = 0;

Когда я начну наблюдать свойство aspect на targetObject (экземпляр TargetClass), у меня будет

[targetObject addObserver:self
               forKeyPath:PFXKeyTargetClassAspect
                  options://...
                  context:&ClassNameKVOContext];

где PFXKeyTargetClassAspect - это NSString *, определенный в TargetClass.m, равный @"aspect" и объявленный extern в TargetClass.h. (Конечно, PFX является просто заполнителем для префикса, который вы используете в своем проекте.) Это дает мне преимущество автозаполнения и защищает меня от опечаток.

Когда я закончу наблюдение aspect на targetObject, у меня будет

[targetObject removeObserver:self
                  forKeyPath:PFXKeyTargetClassAspect
                     context:&ClassNameKVOContext];

Чтобы избежать слишком большого отступа в моей реализации -observeValueForKeyPath:ofObject:change:context:, мне нравится писать

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context != &ClassNameKVOContext) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }

    if ([object isEqual:targetObject]) {
        if ([keyPath isEqualToString:PFXKeyTargetClassAspect]) {
            //targetObject has changed the value for the key @"aspect".
            //do something about it
        }
    }
}

Ответ 3

Я думаю, что лучшим способом было бы реализовать его как apple doc sais:

Адрес статической переменной с уникальным именем в вашем классе создает хороший контекст.

static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;

см. documentation.