Наблюдение NSMutableArray для вставки / удаления - PullRequest
74 голосов
/ 19 ноября 2008

Класс имеет свойство (и экземпляр 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 ]

79 голосов
/ 20 ноября 2008

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

номер

Какой правильный способ обойти это - я должен написать собственный аксессор, который просто вызывает [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

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

9 голосов
/ 19 ноября 2008

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

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

7 голосов
/ 31 октября 2013

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

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

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

3 голосов
/ 17 июня 2010

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

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

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

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

2 голосов
/ 17 апреля 2014

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

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

0 голосов
/ 11 октября 2013

Одним из решений является использование 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 хранит только указатели, это не сильно увеличивает затраты, если вы не работаете с большими массивами и частыми операциями

0 голосов
/ 19 ноября 2008

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

...