2008-11-19 15 views
73

Класс имеет свойство (и экземпляр var) типа NSMutableArray с синтезированными аксессуарами (через @property). Если вы наблюдаете этот массив с помощью:Наблюдение за NSMutableArray для вставки/удаления

[myObj addObserver:self forKeyPath:@"theArray" options:0 context:NULL]; 

И затем вставить объект в массиве, как это:

[myObj.theArray addObject:NSString.string]; 

An observeValueForKeyPath ... уведомление не отправлено. Тем не менее, на следующий же отправить надлежащее уведомление:

[[myObj mutableArrayValueForKey:@"theArray"] addObject:NSString.string]; 

Это происходит потому, что mutableArrayValueForKey возвращает прокси-объект, который заботится уведомления наблюдателей.

Но не должны ли синтезированные аксессоры автоматически возвращать такой прокси-объект? Каков правильный способ обойти это - следует ли писать пользовательский аксессуар, который вызывает только [super mutableArrayValueForKey...]?

ответ

77

Но не должны ли синтезированные аксессоры автоматически возвращать такой прокси-объект?

No.

Что такое правильный способ обойти эту проблему - я должен написать собственный аксессор, что только вызывающую [super mutableArrayValueForKey...]?

No. Внесите array accessors. Когда вы их назовете, KVO автоматически опубликует соответствующие уведомления. Поэтому все, что вам нужно сделать, это:

[myObject insertObject:newObject inTheArrayAtIndex:[myObject countOfTheArray]]; 

и правая вещь произойдет автоматически.

Для удобства вы можете написать аксессуар addTheArrayObject:.Это сбруя бы назвать одним из аксессоров реального массива, описанных выше:

- (void) addTheArrayObject:(NSObject *) newObject { 
    [self insertObject:newObject inTheArrayAtIndex:[self countOfTheArray]]; 
} 

(. Вы можете и должны заполнить соответствующий класс для объектов в массиве, вместо NSObject)

Тогда вместо от [myObject insertObject:…], вы пишете [myObject addTheArrayObject:newObject].

К сожалению, add<Key>Object: и его коллега remove<Key>Object: являются последними, что я проверил, только признанные KVO для свойств set (как в NSSet), а не свойства массива, поэтому вы не получаете от них бесплатных уведомлений KVO, если вы их не реализуете верхней части аксессуаров, которые он распознает. Я подал ошибку об этом: x-radar: // problem/6407437

У меня a list of all the accessor selector formats в моем блоге.

+0

Большой совет, спасибо. –

+5

Проблема №1 заключается в том, что при добавлении наблюдателя вы наблюдаете свойство какого-либо объекта. Массив - это значение * этого свойства, а не свойство. Вот почему вам нужно либо использовать accessors, либо -mutableArrayValueForKey: изменить массив. –

+0

Ваш последний пункт кажется устаревшим - я получаю бесплатные уведомления KVO о свойствах NSArray, если я реализую как добавление, так и удаление аксессуаров. – Bryan

0

Вам необходимо обернуть свой addObject: звонок в willChangeValueForKey: и didChangeValueForKey: звонков. Насколько я знаю, для NSMutableArray вы не можете узнать о каких-либо наблюдателях, наблюдающих за своим владельцем.

9

Я не использовал бы willChangeValueForKey и didChangeValueForKey в этой ситуации. Во-первых, они предназначены для указания того, что значение на этом пути изменилось, а не изменения значений во многих отношениях. Вместо этого вы бы захотели использовать willChange:valuesAtIndexes:forKey:, если вы сделали это так. Тем не менее, использование ручных уведомлений KVO, подобных этому, - плохая инкапсуляция. Лучший способ сделать это - определить метод addSomeObject: в классе, который фактически владеет массивом, который будет включать в себя уведомления KVO вручную. Таким образом, внешние методы, добавляющие объекты в массив, не должны беспокоиться о том, как обращаться с KVO владельца массива, что не будет очень интуитивно понятным и может привести к ненужному коду и, возможно, к ошибкам, если вы начнете добавлять объекты к массив из нескольких мест.

В этом примере я бы продолжал использовать mutableArrayValueForKey:. Я не уверен в изменяемых массивах, но, по-моему, я читал документацию о том, что этот метод фактически заменяет весь массив новым объектом, поэтому, если производительность вызывает озабоченность, вы также захотите реализовать insertObject:in<Key>AtIndex: и removeObjectFrom<Key>AtIndex: в классе, который владеет массив.

3

Ваш собственный ответ на ваш вопрос почти прав. Не используйте внешний продукт theArray. Вместо этого, объявить другое свойство, theMutableArray, что соответствует не переменной экземпляра, и записать эту аксессор:

- (NSMutableArray*) theMutableArray { 
    return [self mutableArrayValueForKey:@"theArray"]; 
} 

Результатом является то, что другие объекты могут использовать thisObject.theMutableArray, чтобы внести изменения в массиве, и эти изменения вызвать KVO.

Другие ответы указывают на то, что эффективность увеличивается, если вы также реализуете insertObject:inTheArrayAtIndex: и removeObjectFromTheArrayAtIndex: по-прежнему верны. Но нет необходимости в том, чтобы другие объекты должны были знать об этом или называть их напрямую.

+0

При использовании этого ярлыка я могу наблюдать только изменения в ключевом пути «theArray», а не «theMutableArray». –

+0

Кроме того, все работает отлично, и я могу манипулировать 'theMutableArray' так же, как если бы это было свойство' NSMutableArray'. –

+0

Когда я пытаюсь лениво инициализировать этот прокси-объект, никакое уведомление KVO не испускается. Ты знаешь почему? - (NSMutableArray *) theMutableArray { if (_theMutableArray) return _theMutableArray; _theMutableArray = [self mutableArrayValueForKey: @ "theArray"]; return _theMutableArray; } –

0

одно решение заключается в использовании NSArray и создать его с нуля, вставки и удаления, как

- (void)addSomeObject:(id)object { 
    self.myArray = [self.myArray arrayByAddingObject:object]; 
} 

- (void)removeSomeObject:(id)object { 
    NSMutableArray * ma = [self.myArray mutableCopy]; 
    [ma removeObject:object]; 
    self.myArray = ma; 
} 

чем вы получите КВО и можете сравнить старый и новый массив

ПРИМЕЧАНИЯ: self.myArray не должно быть nil, иначе arrayByAddingObject: результаты тоже отсутствуют

В зависимости от случая это может быть решением, и поскольку NSArray хранит только указатели, это не так много накладных расходов, если вы не работаете с большими массивами и частые операции

5

, когда вы просто хотите наблюдать счетчик изменился, вы можете использовать агрегатную ключевой путь:

[myObj addObserver:self forKeyPath:@"[email protected]" options:0 context:NULL]; 

но имейте в виду, что любое изменение порядка в theArray не будет срабатывать.

+0

Или замены в этом случае, например. когда используется [-replaceObjectAtIndex: withObject:]. –

2

Если вам не нужен сеттер, вы также можете использовать более простую форму ниже, которая имеет аналогичную производительность (то же самое growth rate в моих тестах) и меньше шаблона.

// Interface 
@property (nonatomic, strong, readonly) NSMutableArray *items; 

// Implementation 
@synthesize items = _items; 

- (NSMutableArray *)items 
{ 
    return [self mutableArrayValueForKey:@"items"]; 
} 

// Somewhere else 
[myObject.items insertObject:@"test"]; // Will result in KVO notifications for key "items" 

Это работает, потому что если аксессоры массива не реализованы и не сеттер для ключа, mutableArrayValueForKey: будет искать переменный экземпляр с именем _<key> или <key>. Если он найдет один, прокси отправит все сообщения этому объекту.

См. these Apple docs, раздел «Шаблон поиска доступа для упорядоченных коллекций», № 3.

+0

По какой-то причине это не работает для меня. В этом случае я мог бы быть действительно слепым. – Entalpi

+1

При использовании этого решения обязательно отметьте свои свойства как 'readonly', иначе это будет зависеть от бесконечного цикла ... – Symaxion