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

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

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

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

[myObj.theArray addObject:NSString.string];

Уведомление наблюдать за значением для ключа... не отправляется. Тем не менее, следующее отправляет соответствующее уведомление:

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

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

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

7 ответов

Решение

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

Нет.

Какой правильный способ обойти это - я должен написать собственный аксессор, который просто вызывает [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 только для установленных (как в NSSet) свойств, а не для свойств массива, поэтому вы не получите с ними бесплатные уведомления KVO, если не внедрите их поверх методов доступа, которые он распознает. Я подал ошибку об этом: x-radar://problem/6407437

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

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

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

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

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

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

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

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

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

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

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

// 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.

Одним из решений является использование 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: также приводит к значению nil

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

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

Другие вопросы по тегам