2012-04-07 18 views
7

Desidero ricevere una notifica quando il conteggio, ad es. numero di elementi in un NSArray cambia .. Ovviamente non avrei bisogno di questo, se avessi il controllo dell'aggiunta e della rimozione di oggetti nell'array. Ma non lo sono, accade in modo imprevedibile per quanto riguarda il modello di processo aziendale e dipende da fattori esterni. Esiste una soluzione semplice ed elegante?Osservazione del conteggio in NSMutableArray

EDIT: Sto correggendo questo NSMutableArray ovviamente ..

+0

Non sono al 100% su questo, ma un keyPath a un array e il suffisso '@ count' è il modo KVC per ottenere questo valore. Quindi forse puoi KVO osservare 'array @ count'? https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueCoding/Articles/CollectionOperators.html#//apple_ref/doc/uid/20002176-BAJEAIEE – joerick

risposta

15

avrete bisogno di utilizzare KVC. Ma come fare per farlo? Dopotutto, NSMutableArray non è compatibile con la codifica della chiave per i suoi metodi di mutazione o modifiche dei contenuti. La risposta è proxying -come sottoclassi NS [Mutable] Array è troppo di una seccatura.

NSProxy è una piccola grande classe che è possibile utilizzare per intercettare i messaggi inviati all'array come se si fosse un NSMutableArray, quindi inoltrarli a un'istanza interna. Sfortunatamente, non è nemmeno conforme a KVC, dal momento che l'intestino di KVC vive in NSObject. Dovremo usarlo, allora. Un'interfaccia di esempio potrebbe essere simile a questa:

@interface CFIKVCMutableArrayProxy : NSObject { 
    NSMutableArray *_innerArray; 
} 

- (NSUInteger)count; 

- (void)insertObject:(id)anObject atIndex:(NSUInteger)index; 
- (void)removeObjectAtIndex:(NSUInteger)index; 
- (void)addObject:(id)anObject; 
- (void)removeLastObject; 
- (void)insertObjects:(NSArray *)objects atIndexes:(NSIndexSet *)indexes; 
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(id)anObject; 

//… 

@end 

Come potete vedere, stiamo simulando un'interfaccia per NSMutableArray, che è necessaria, come la nostra procura dovrebbe attuare ogni cosa come se fosse un NSMutableArray. Ciò rende anche l'implementazione il più semplice possibile, in quanto possiamo semplicemente inoltrare i selettori al nostro puntatore interno NSMutableArray. Per ragioni di brevità, io implementare solo due metodi per mostrare ciò che un quadro generale appare come:

@implementation CFIKVCMutableArrayProxy 

//… 

- (NSUInteger)count { 
    return _innerArray.count; 
} 

- (void)addObject:(id)anObject { 
    [self willChangeValueForKey:@"count"]; 
    [_innerArray addObject:anObject]; 
    [self didChangeValueForKey:@"count"]; 
} 

- (void)removeLastObject { 
    [self willChangeValueForKey:@"count"]; 
    [_innerArray removeLastObject]; 
    [self didChangeValueForKey:@"count"]; 
} 

@end 

Se non ci sono opportunità per avvolgere una serie come questa, quindi provare a ripensare il proprio codice . Se una dipendenza esterna ti sta costringendo in questo tipo di angolo, prova a rimuoverlo. È sempre una brutta cosa aggirare i tuoi stessi strumenti.

+0

Qualche tempo è passato e un fortunato miglioramento essere implementato da parte del codice che è fuori dal mio controllo - l'oggetto facciata che contiene l'array ora emetterà NSNotifications quando il modello cambia. La vostra soluzione proposta è degna di nota, ma vi siete persi il piccolo ma importante fatto che non ero in controllo della matrice. Quindi non è stato possibile per me iniettare l'oggetto proxy sulla facciata anziché sulla matrice. –

+0

Ho bisogno di imparare qualcosa di nuovo. – naz

+0

@CodaFi Grazie per la spiegazione :) –

6

Per osservare i cambiamenti in uno mutableArray deve utilizzare oggetto proxy mutabile in

- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key 

che è conforme KVO, cioè ogni cambiamento di oggetto proxy invia volontà/fatto cambiare notifiche.

Il seguente classe di demo mostrata la piena attuazione

@interface DemoClass : NSObject 

@property (nonatomic) NSMutableArray *items; 

- (void)addItemsObserver:(id)object; 
- (void)removeItemsObserver:(id)object; 

@end 

@implementation DemoClass 

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

- (void)addItemsObserver:(id)object 
{ 
    [self addObserver:object forKeyPath:@"[email protected]" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:nil]; 
} 

- (void)removeItemsObserver:(id)object 
{ 
    [self removeObserver:object forKeyPath:@"[email protected]" context:nil]; 
} 
@end 


@interface ObservingClass : NSObject 

@property (nonatomic) DemoClass *demoObject; 

@end 

@implementation ObservingClass 

- (instanstype)init 
{ 
    if (self = [super init]) { 
     _demoObject = [DemoClass new]; 

     [_demoObject addItemsObserver:self]; 
    } 
    return self; 
} 

- (void)observeValueForKeyPath:(NSString *)keyPath 
        ofObject:(id)object 
        change:(NSDictionary *)change 
        context:(void *)context 
{ 
    NSLog(@"is called on demoObject.items.count change"); 
} 

- (void)dealloc 
{ 
    [_demoObject removeItemsObserver:self]; 
} 

@end 

Ora ogni volta che si aggiunge o rimuove un oggetto nella items vedrete nuovo log in console (observeValueForKeyPath si chiama).

Qualsiasi modifica diretta della matrice di ivar auto sintetizzata _items non avrà alcun effetto.

Si noti inoltre che è necessario impostare l'osservatore su [email protected] (l'osservazione di [email protected] non ha senso).

Nota che è necessario non avviare _items o self.items. Sarà fatto dietro la scena quando chiami items getter.

Ogni volta che si modifica la "matrice" items si otterrà nuovo oggetto _items con nuovo indirizzo. Ma posso ancora trovarlo tramite getter proxy items.

+0

Si consiglia anche un metodo 'removeProxyItemsObserver:'. –

+0

@AaronBrager, appena aggiunto per completezza. Grazie. – malex

Problemi correlati