Observación de NSMutableArray para su inserción / extracción

76

Una clase tiene una propiedad (y var de instancia) de tipo NSMutableArray con descriptores de acceso sintetizados (via @property). Si observa esta matriz usando:

Y luego inserte un objeto en la matriz como este:

Un observeValueForKeyPath ... notificación no se ha enviado. Sin embargo, lo siguiente envía la notificación adecuada:

Esto se debe a que mutableArrayValueForKeydevuelve un objeto proxy que se encarga de notificar a los observadores.

Pero, ¿no deberían los accesores sintetizados devolver automáticamente tal objeto proxy? ¿Cuál es la forma correcta de evitar esto? ¿Debería escribir un descriptor de acceso personalizado que simplemente invoque [super mutableArrayValueForKey...]?

Adam Ernst
fuente

Respuestas:

79

Pero, ¿no deberían los accesores sintetizados devolver automáticamente tal objeto proxy?

No.

¿Cuál es la forma correcta de evitar esto? ¿Debería escribir un descriptor de acceso personalizado que simplemente invoque [super mutableArrayValueForKey...]?

No. Implemente los descriptores de acceso a la matriz . Cuando los llame, KVO publicará las notificaciones correspondientes automáticamente. Entonces todo lo que tienes que hacer es:

y lo correcto sucederá automáticamente.

Para mayor comodidad, puede escribir un addTheArrayObject:descriptor de acceso. Este descriptor de acceso llamaría a uno de los descriptores de acceso de matriz reales descritos anteriormente:

(Puede y debe completar la clase adecuada para los objetos de la matriz, en lugar de NSObject).

Entonces, en lugar de [myObject insertObject:…], escribe [myObject addTheArrayObject:newObject].

Lamentablemente, add<Key>Object:y su contraparte remove<Key>Object:, la última vez que verifiqué, solo la reconoce KVO para las propiedades del conjunto (como en NSSet), no las propiedades de la matriz, por lo que no recibe notificaciones KVO gratuitas con ellas a menos que las implemente en la parte superior de los accesos que sí reconoce. . Presenté un error sobre esto: x-radar: // problem / 6407437

Tengo una lista de todos los formatos de selector de acceso en mi blog.

Peter Hosey
fuente
5
El problema # 1 es que cuando agrega un observador, está observando una propiedad de algún objeto. La matriz es el valor de esa propiedad, no la propiedad en sí. Es por eso que necesita usar los descriptores de acceso o -mutableArrayValueForKey: para modificar la matriz.
Chris Hanson
Su último punto parece estar desactualizado: obtengo notificaciones KVO gratuitas en las propiedades de NSArray, siempre que implemente tanto agregar como eliminar accesos.
Bryan
9

No lo usaría willChangeValueForKeyy didChangeValueForKeyen esta situación. Por un lado, están destinados a indicar que el valor en esa ruta ha cambiado, no que los valores en una relación a muchos estén cambiando. En su willChange:valuesAtIndexes:forKey:lugar, querría usarlo si lo hiciera de esta manera. Aun así, el uso de notificaciones KVO manuales como esta es una mala encapsulación. Una mejor forma de hacerlo es definir un método addSomeObject:en la clase que realmente posee la matriz, que incluiría las notificaciones KVO manuales. De esta manera, los métodos externos que agregan objetos a la matriz no necesitan preocuparse por manejar el KVO del propietario de la matriz también, lo cual no sería muy intuitivo y podría generar código innecesario y posiblemente errores si comienza a agregar objetos a la matriz. Matriz de varios lugares.

En este ejemplo, continuaría usando mutableArrayValueForKey:. No estoy seguro con las matrices mutables, pero creo que, al leer la documentación, este método en realidad reemplaza toda la matriz con un nuevo objeto, por lo que si el rendimiento es una preocupación, también querrá implementar insertObject:in<Key>AtIndex:y removeObjectFrom<Key>AtIndex:en la clase que posee la matriz. .

Marc Charbonneau
fuente
7

cuando solo desee observar el cambio en el recuento, puede usar una ruta de clave agregada:

pero tenga en cuenta que no se activará ningún reordenamiento en el Array.

berbie
fuente
O reemplazos para el caso, por ejemplo, cuando se usa [-replaceObjectAtIndex: withObject:].
Patrick Pijnappel
3

Tu propia respuesta a tu propia pregunta es casi correcta. No venda theArrayexternamente. En su lugar, declare una propiedad diferente, theMutableArrayque no corresponda a ninguna variable de instancia, y escriba este descriptor de acceso:

El resultado es que otros objetos pueden usar thisObject.theMutableArraypara realizar cambios en la matriz, y estos cambios activan KVO.

Las otras respuestas señalan que la eficiencia aumenta si también implementa insertObject:inTheArrayAtIndex:y removeObjectFromTheArrayAtIndex:sigue siendo correcto. Pero no es necesario que otros objetos tengan que conocerlos o llamarlos directamente.

mate
fuente
Cuando utilizo este atajo, solo puedo observar cambios en la ruta clave "theArray", no en "theMutableArray".
Michael Mior
Aparte de eso, todo funciona perfectamente y puedo manipular theMutableArraycomo si fuera una NSMutableArraypropiedad.
Michael Mior
Cuando intento iniciar de forma diferida este objeto proxy, no se emite ninguna notificación de KVO. ¿Sabes por qué? - (NSMutableArray *) theMutableArray {if (_theMutableArray) return _theMutableArray; _theMutableArray = [self mutableArrayValueForKey: @ "theArray"]; return _theMutableArray; }
Luong Huy Duc
2

Si no necesita el setter, también puede usar el formulario más simple a continuación, que tiene un rendimiento similar (la misma tasa de crecimiento en mis pruebas) y menos repetitivo.

Esto funciona porque si los descriptores de acceso a la matriz no están implementados y no hay un definidor para la clave, mutableArrayValueForKey:buscará una variable de instancia con el nombre _<key>o <key>. Si encuentra uno, el proxy reenviará todos los mensajes a este objeto.

Consulte estos documentos de Apple , sección "Patrón de búsqueda de acceso para colecciones ordenadas", n.º 3.

Patrick Pijnappel
fuente
Por alguna razón, esto no me funciona. Aunque podría ser realmente ciego en este caso.
Entalpi
1
Cuando use esta solución, asegúrese de marcar sus propiedades como readonly, o de lo contrario, se atascará en un bucle infinito ...
Symaxion
0

Necesita completar su addObject:llamada willChangeValueForKey:y sus didChangeValueForKey:llamadas. Hasta donde yo sé, no hay forma de que el NSMutableArray que está modificando sepa si hay observadores que observan a su propietario.

Ben Gottlieb
fuente
0

una solución es usar un NSArray y crearlo desde cero insertando y quitando, como

de lo que obtiene el KVO y puede comparar la matriz antigua y la nueva

NOTA: self.myArray no debe ser nulo, de lo contrario arrayByAddingObject: también da como resultado nil

Dependiendo del caso, esta podría ser la solución, y como NSArray solo almacena punteros, esto no es una sobrecarga, a menos que trabaje con arreglos grandes y operaciones frecuentes.

Peter Lapisu
fuente