Observando um NSMutableArray para inserção / remoção

Uma class tem uma propriedade (e instância var) do tipo NSMutableArray com accessres sintetizados (via @property ). Se você observar este array usando:

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

E então insira um object no array assim:

 [myObj.theArray addObject:NSString.string]; 

Uma notificação de observValueForKeyPath … não é enviada. No entanto, o seguinte envia a notificação apropriada:

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

Isso ocorre porque mutableArrayValueForKey retorna um object proxy que cuida de notificar os observadores.

Mas os accessres sintetizados não devem retornar automaticamente um object proxy? Qual é a maneira correta de contornar isso – devo escrever um acessador personalizado que invoca [super mutableArrayValueForKey...] ?

Mas os accessres sintetizados não devem retornar automaticamente um object proxy?

Não.

Qual é a maneira correta de contornar isso – devo escrever um acessador personalizado que invoca [super mutableArrayValueForKey...] ?

Não. Implemente os accessres da matriz . Quando você liga para eles, a KVO postará as notifications apropriadas automaticamente. Então tudo que você precisa fazer é:

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

e a Coisa Certa acontecerá automaticamente.

Por conveniência, você pode escrever um addTheArrayObject: Este accessr chamaria um dos accessres da matriz real descritos acima:

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

(Você pode e deve preencher a class apropriada para os objects na matriz, no lugar do NSObject .)

Então, em vez de [myObject insertObject:…] , você escreve [myObject addTheArrayObject:newObject] .

Infelizmente, addObject: e sua contraparte removeObject: são, pela última vez que verifiquei, somente reconhecido pelo KVO para propriedades set (como no NSSet), não propriedades de array, para que você não receba notifications KVO gratuitas com eles, a menos que você os implemente em cima dos acessadores que ele reconhece. Eu arquivei um bug sobre isso: x-radar: // problem / 6407437

Eu tenho uma lista de todos os formatos de seletor de acessador no meu blog.

Eu não usaria willChangeValueForKey e didChangeValueForKey nessa situação. Por um lado, eles são destinados a indicar que o valor nesse caminho mudou, e não que os valores em um relacionamento para muitos estão mudando. Você desejaria usar willChange:valuesAtIndexes:forKey: vez disso, se você fez dessa maneira. Mesmo assim, usar notifications manuais do KVO como essa é um mau encapsulamento. Uma maneira melhor de fazer isso é definir um método addSomeObject: na class que realmente possui o array, que includeia as notifications manuais do KVO. Dessa forma, os methods externos que estão adicionando objects à matriz não precisam se preocupar em lidar com o KVO do proprietário da matriz, o que não seria muito intuitivo e poderia levar a código desnecessário e possivelmente bugs se você começar a adicionar objects à matriz de vários lugares.

Neste exemplo, eu continuaria usando mutableArrayValueForKey: Eu não sou positivo com matrizes mutáveis, mas acredito que ao ler a documentação que este método realmente substitui o array inteiro por um novo object, então se o desempenho é uma preocupação você também vai querer implementar o insertObject:inAtIndex: e removeObjectFromAtIndex: na class que possui a matriz.

quando você quiser apenas observar a contagem alterada, você pode usar um caminho de chave agregada:

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

mas esteja ciente de que qualquer reordenação no Array não será triggersda.

Sua própria resposta à sua própria pergunta está quase certa. Não theArray o theArray externamente. Em vez disso, declare uma propriedade diferente, theMutableArray , correspondente a nenhuma variável de instância e escreva este accessr:

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

O resultado é que outros objects podem usar thisObject.theMutableArray para fazer alterações na matriz, e essas alterações acionam o KVO.

As outras respostas apontam que a eficiência é aumentada se você também implementar insertObject:inTheArrayAtIndex: e removeObjectFromTheArrayAtIndex: ainda estão corretos. Mas não há necessidade de outros objects para saber sobre eles ou chamá-los diretamente.

Se você não precisa do setter, você também pode usar o formulário mais simples abaixo, que tem desempenho similar (mesma taxa de crescimento em meus testes) e menor clichê.

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

Isso funciona porque, se os acessadores de matriz não forem implementados e não houver nenhum mutableArrayValueForKey: para a chave, mutableArrayValueForKey: procurará uma variável de instância com o nome _ ou . Se encontrar um, o proxy encaminhará todas as mensagens para este object.

Veja estes docs da Apple , seção “Padrão de pesquisa do Accessor para collections ordenadas”, nº 3.

Você precisa addObject: suas chamadas addObject: call em willChangeValueForKey: e didChangeValueForKey: Até onde eu sei, não há como o NSMutableArray saber sobre qualquer observador que esteja observando seu dono.

Uma solução é usar um NSArray e criá-lo do zero, inserindo e removendo, como

 - (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; } 

do que você obtém o KVO e pode comparar o antigo e o novo array

NOTA: self.myArray não deve ser nulo, senão arrayByAddingObject: resulta em nada também

Dependendo do caso, essa pode ser a solução e, como o NSArray está apenas armazenando pointers, isso não é muito uma sobrecarga, a menos que você trabalhe com grandes matrizes e operações freqüentes.