2008-11-19 8 views
73

Una classe ha una proprietà (e un'istanza var) di tipo NSMutableArray con accessors sintetizzati (tramite @property). Se si osserva questa matrice utilizzando:Osservazione di un NSMutableArray per l'inserimento/rimozione

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

e quindi inserire un oggetto nella matrice come questo:

[myObj.theArray addObject:NSString.string]; 

Un observeValueForKeyPath ... notifica è non inviato. Tuttavia, il seguente fa inviare la corretta notifica:

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

Questo perché mutableArrayValueForKey restituisce un oggetto proxy che si prende cura di notificare gli osservatori.

Ma gli accessori sintetizzati non dovrebbero restituire automaticamente tale oggetto proxy? Qual è il modo corretto per ovviare a questo: dovrei scrivere una funzione di accesso personalizzata che invochi solo [super mutableArrayValueForKey...]?

risposta

77

Ma gli accessori sintetizzati non dovrebbero restituire automaticamente tale oggetto proxy?

No.

Qual è il modo corretto per risolvere questo - dovrei scrivere una funzione di accesso personalizzato che solo invoca [super mutableArrayValueForKey...]?

No. Implementare il array accessors. Quando li chiami, KVO pubblicherà automaticamente le notifiche appropriate. Quindi tutto ciò che dovete fare è:

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

e la cosa giusta accadrà automaticamente.

Per comodità, è possibile scrivere un accessorio addTheArrayObject:.Questo di accesso avrebbe chiamato una delle funzioni di accesso reale matrice sopra descritti:

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

(. Si può e si deve compilare la classe corretta per gli oggetti nella matrice, al posto di NSObject)

Poi, invece di [myObject insertObject:…], si scrive [myObject addTheArrayObject:newObject].

Purtroppo, add<Key>Object: e la sua controparte remove<Key>Object: sono, ultimo ho controllato, solo riconosciuto da KVO per set (come in NSSet) proprietà, e non proprietà di matrice, quindi non si ottiene gratis avvisi KVO con loro a meno che non si sceglie di implementare loro su cima di accessors che riconosce. Ho presentato un bug su questo: x-radar: // problema/6407437

Ho il mio a list of all the accessor selector formats sul mio blog.

+0

Ottimo consiglio, grazie. –

+5

Il problema n. 1 è che quando aggiungi un osservatore, stai osservando una proprietà di qualche oggetto. L'array è il * valore * di quella proprietà, non la proprietà stessa. Ecco perché è necessario utilizzare gli accessors o -mutableArrayValueForKey: per modificare l'array. –

+0

L'ultimo punto sembra non essere aggiornato: ottengo notifiche KVO gratuite sulle proprietà NSArray, a condizione che implemento sia l'aggiunta che la rimozione degli accessor. – Bryan

0

È necessario avvolgere la chiamata addObject: nelle chiamate willChangeValueForKey: e didChangeValueForKey:. Per quanto ne so, non c'è modo per NSMutableArray che stai modificando per sapere di eventuali osservatori che osservano il suo proprietario.

9

Non utilizzare willChangeValueForKey e didChangeValueForKey in questa situazione. Per uno, hanno lo scopo di indicare che il valore di quel percorso è cambiato, non che i valori in una relazione to-many stanno cambiando. Si vorrebbe usare willChange:valuesAtIndexes:forKey: invece, se l'hai fatto in questo modo. Anche così, l'uso di notifiche KVO manuali come questa è incapsulamento errato. Un modo migliore per farlo è definire un metodo addSomeObject: nella classe che possiede effettivamente l'array, che includerebbe le notifiche KVO manuali. In questo modo, i metodi esterni che aggiungono oggetti all'array non devono preoccuparsi di gestire anche il KVO del proprietario dell'array, il che non sarebbe molto intuitivo e potrebbe portare a codice non necessario e possibili bug se si inizia ad aggiungere oggetti al array da diversi posti.

In questo esempio, in effetti, continuerò a utilizzare mutableArrayValueForKey:. Non sono positivo con gli array mutabili, ma credo che leggendo la documentazione che questo metodo sostituisca effettivamente l'intero array con un nuovo oggetto, quindi se le prestazioni sono un problema, ti consigliamo di implementare anche insertObject:in<Key>AtIndex: e removeObjectFrom<Key>AtIndex: nella classe che possiede l'array.

3

La tua risposta alla tua domanda è quasi giusta. Non vendere theArray esternamente. Invece, dichiarare una struttura diversa, theMutableArray, corrispondente all'assenza variabile di istanza, e scrivere questo di accesso:

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

Il risultato è che altri oggetti possono utilizzare thisObject.theMutableArray per apportare modifiche alla matrice, e questi cambiamenti innescare KVO.

Le altre risposte sottolineando che l'efficienza aumenta se si implementano anche insertObject:inTheArrayAtIndex: e removeObjectFromTheArrayAtIndex: sono ancora corretti. Ma non c'è bisogno che altri oggetti debbano saperlo o chiamarli direttamente.

+0

Quando si utilizza questo collegamento, sono solo in grado di osservare le modifiche sul percorso chiave "theArray", non "theMutableArray". –

+0

Oltre a ciò, tutto funziona perfettamente e posso manipolare 'theMutableArray' proprio come se fosse una proprietà' NSMutableArray'. –

+0

Quando provo a avviare pigro questo oggetto proxy, non viene emessa alcuna notifica KVO. Sai perché? - (NSMutableArray *) theMutableArray { if (_theMutableArray) return _theMutableArray; _theMutableArray = [self mutableArrayValueForKey: @ "theArray"]; return _theMutableArray; } –

0

una soluzione è quella di utilizzare un NSArray e creare da zero con l'inserimento e la rimozione, come

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

di quanto si ottiene il KVO e possibile confrontare vecchio e nuovo array

NOTA: self.myArray non dovrebbe essere nullo, altrimenti arrayByAddingObject: risultati in nil anche

A seconda dei casi, questa potrebbe essere la soluzione e poiché NSArray memorizza solo i puntatori, questo non è un sovraccarico, a meno che non si lavori con array di grandi dimensioni e operazioni frequenti

5

quando si desidera solo osservare il conteggio è cambiato, è possibile utilizzare un percorso chiave di aggregazione:

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

ma essere consapevoli che qualsiasi riordinamento in theArray non scatta mai.

+0

o sostituzioni per quella materia, ad es. quando viene usato [-replaceObjectAtIndex: withObject:]. –

2

Se non è necessario il setter, è possibile utilizzare anche il modulo più semplice di seguito, che ha prestazioni simili (lo stesso growth rate nei miei test) e meno standard.

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

Questo funziona perché se le funzioni di accesso matrice non sono attuate e non c'è setter per la chiave, mutableArrayValueForKey: cercherà una variabile di istanza con il nome _<key> o <key>. Se trova uno, il proxy inoltrerà tutti i messaggi a questo oggetto.

Vedere these Apple docs, sezione "Modello di ricerca degli accessori per raccolte ordinate", # 3.

+0

Per qualche motivo questo non funziona per me. Potrei essere davvero cieco in questo caso però. – Entalpi

+1

Quando si utilizza questa soluzione, assicurarsi di contrassegnare le proprietà come 'readonly', altrimenti questo sarà bloccato in un ciclo infinito ... – Symaxion