2008-11-19 5 views
73

Klasa ma właściwość (i instancję var) typu NSMutableArray z syntezowanymi akcesoriami (przez @property). Jeśli obserwować tę tablicę używając:Obserwacja obiektu NSMutableArray do wstawiania/usuwania

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

A potem wstawić obiekt w tablicy tak:

[myObj.theArray addObject:NSString.string]; 

... An observeValueForKeyPath zgłoszenie jest nie wysłana. Jednak dodaje robi wysyłać prawidłowe zgłoszenie:

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

To dlatego mutableArrayValueForKey zwraca obiekt proxy, który dba o powiadamianie obserwatorów.

Ale czy syntetyzowane dodatki nie powinny automatycznie zwracać takiego obiektu proxy? Jaki jest właściwy sposób obejścia tego problemu - czy powinienem napisać niestandardowy akcesor, który po prostu wywołuje [super mutableArrayValueForKey...]?

Odpowiedz

77

Ale czy syntetyzowane dodatki nie powinny automatycznie zwracać takiego obiektu proxy?

nr

Jaki jest właściwy sposób, aby obejść ten problem - mam napisać niestandardowy akcesor że właśnie wywołuje [super mutableArrayValueForKey...]?

Nie. Wdrożenie array accessors. Po ich wywołaniu KVO automatycznie opublikuje odpowiednie powiadomienia. Wszystko, co musisz zrobić, to:

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

, a właściwa rzecz nastąpi automatycznie.

Dla wygody można napisać akcesorium addTheArrayObject:.Ten accessor nazwałbym jeden z akcesorów prawdziwy tablicy opisanych powyżej:

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

(. Można i należy wypełnić w odpowiedniej klasie obiektów w tablicy, w miejscu NSObject)

Następnie, zamiast [myObject insertObject:…], piszesz [myObject addTheArrayObject:newObject].

Niestety, add<Key>Object: i jego odpowiednik są, ostatnio sprawdziłem, rozpoznawane tylko przez KVO dla zestawu (jak w NSSet) właściwości, nie właściwości tablicy, więc nie otrzymasz z nimi darmowych powiadomień KVO, chyba że zastosujesz je na szczyt akcesorów rozpoznaje. Złożyłem błąd na ten temat: x-radar: // problem/6407437

Mam a list of all the accessor selector formats na moim blogu.

+0

Świetna rada, dzięki. –

+5

Problem nr 1 polega na tym, że dodając obserwatora, obserwujesz właściwość jakiegoś obiektu. Tablica jest wartością * tej właściwości, a nie samą właściwością. Dlatego musisz użyć akcesorów lub -mutableArrayValueForKey: aby zmodyfikować tablicę. –

+0

Twoja ostatnia kwestia wydaje się być nieaktualna - otrzymuję bezpłatne powiadomienia KVO o właściwościach NSArray, pod warunkiem, że zaimplementuję zarówno dodawanie, jak i usuwanie akcesorów. – Bryan

0

Musisz zawinąć swoje wywołanie addObject: w rozmowach willChangeValueForKey: i didChangeValueForKey:. O ile wiem, nie ma możliwości, aby NSMutableArray, którą modyfikujesz, wiedział o obserwatorach obserwujących właściciela.

9

Nie używałbym w takich sytuacjach willChangeValueForKey i didChangeValueForKey. Po pierwsze, mają na celu wskazanie, że wartość na tej ścieżce się zmieniła, a nie zmieniają się wartości w relacji "wiele do wielu". Zamiast tego chciałbyś użyć willChange:valuesAtIndexes:forKey:, jeśli zrobiłeś to w ten sposób. Mimo to używanie ręcznych powiadomień KVO jest złe w hermetyzacji. Lepszym sposobem jest zdefiniowanie metody w klasie, która faktycznie jest właścicielem tablicy, która zawierałaby ręczne powiadomienia KVO. W ten sposób metody zewnętrzne, które dodają obiekty do tablicy, również nie muszą martwić się obsługą KVO właściciela tablicy, co nie byłoby zbyt intuicyjne i może prowadzić do niepotrzebnego kodu i prawdopodobnie błędów, jeśli zaczniesz dodawać obiekty do tablica z kilku miejsc.

W tym przykładzie faktycznie będę nadal używać mutableArrayValueForKey:. Nie jestem zadowolony z tablic zmiennych, ale uważam, że po przeczytaniu dokumentacji ta metoda faktycznie zastępuje całą tablicę nowym obiektem, więc jeśli wydajność jest problemem, należy również zaimplementować insertObject:in<Key>AtIndex: i removeObjectFrom<Key>AtIndex: w klasie, która jest właścicielem tablica.

3

Twoja własna odpowiedź na własne pytanie jest prawie w porządku. Nie sprzedawaj zewnętrznie theArray. Zamiast zadeklarować inną nieruchomość, theMutableArray, odpowiadający bez zmiennej instancji i napisać akcesor:

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

Powoduje to, że inne obiekty mogą korzystać thisObject.theMutableArray wprowadzić zmiany do tablicy, a zmiany te wyzwolić KVO.

Pozostałe odpowiedzi wskazujące, że wydajność jest większa, jeśli wdrożono również insertObject:inTheArrayAtIndex: i removeObjectFromTheArrayAtIndex: są nadal poprawne. Ale nie ma potrzeby, aby inne obiekty musiały wiedzieć o tym lub zadzwonić bezpośrednio.

+0

Używając tego skrótu, mogę obserwować zmiany tylko na ścieżce klucza "theArray", a nie "theMutableArray". –

+0

Poza tym wszystko działa idealnie i mogę manipulować 'theMutableArray' tak, jakby była własnością' NSMutableArray'. –

+0

Kiedy próbuję leniwy zainicjować ten obiekt proxy, nie jest wysyłane powiadomienie KVO. Wiesz dlaczego? - (NSMutableArray *) theMutableArray { if (_MutableArray) return _MutableArray; _tabelaMutableArray = [self mutableArrayValueForKey: @ "theArray"]; return _theMutableArray; } –

0

jednym rozwiązaniem jest użycie NSArray i tworzyć je od podstaw wkładania i wyjmowania, jak

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

niż masz KVO i może porównać stare i nowej tablicy

UWAGA: self.myArray should be be nil, else arrayByAddingObject: wyniki zerowe też

W zależności od przypadku może to być rozwiązanie, a ponieważ NSArray zapisuje tylko wskaźniki, nie jest to zbytnio obciążeniem, chyba że pracujesz z dużymi tablicami i częste operacje

5

gdy chcesz po prostu obserwować licznik zmianie, można użyć łączną ścieżkę klucz:

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

ale należy pamiętać, że każda zmiana kolejności w theArray nie zadziała.

+0

Lub zamienniki dla tej sprawy, np. gdy użyto [-replaceObjectAtIndex: withObject:]. –

2

Jeśli nie potrzebujesz settera, możesz również użyć prostszego formularza poniżej, który ma podobną wydajność (ten sam growth rate w moich testach) i mniejszą płytkę.

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

To działa, ponieważ jeśli Akcesory tablicy nie są realizowane i nie ma seter na klucz, mutableArrayValueForKey: będzie szukał zmiennej instancji o nazwie _<key> lub <key>. Jeśli znajdzie taki, proxy przekaże wszystkie wiadomości do tego obiektu.

Zobacz these Apple docs, sekcja "Wzorzec wyszukiwania akcesoriów do uporządkowanych kolekcji", # 3.

+0

Z jakiegoś powodu nie działa to dla mnie. Jednak w tym przypadku mogę być naprawdę ślepy. – Entalpi

+1

Używając tego rozwiązania, upewnij się, że twoje właściwości są oznaczane jako "readonly", bo inaczej utkną w nieskończonej pętli ... – Symaxion