Наблюдение за NSMutableArray для вставки/удаления

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

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

И затем вставьте объект в массив следующим образом:

[myObj.theArray addObject:NSString.string];

Уведомление observValueForKeyPath... отправлено не. Однако следующее сообщение отправляет надлежащее уведомление:

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

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

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

Ответ 1

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

Нет.

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

Нет. Внедрите аксессоры массива. Когда вы их назовете, 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

У меня список всех форматов селектора доступа в моем блоге.

Ответ 2

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

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

Ответ 3

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

// 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>. Если он найдет один, прокси отправит все сообщения этому объекту.

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

Ответ 6

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

Ответ 7

одним из решений является использование 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;
}

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

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

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