2008-11-19 121 views
73

一個類具有NSMutableArray類型的屬性(和實例var)以及合成訪問器(通過@property)。如果您在使用觀察此陣:觀察用於插入/刪除的NSMutableArray

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

然後像這樣的數組中插入的對象:

[myObj.theArray addObject:NSString.string]; 

的observeValueForKeyPath ...通知發送。但是,下列情況發送適當的通知:

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

這是因爲mutableArrayValueForKey返回一個代理對象,需要通知觀察員的照顧。

但是不應該合成訪問器自動返回這樣的代理對象嗎?什麼是解決這個正確的方法 - 我應該寫,只是調用[super mutableArrayValueForKey...]自定義訪問?

回答

77

但是不應該合成訪問器自動返回這樣的代理對象嗎?

什麼是解決這個正確的方法 - 我應該寫,只是調用[super mutableArrayValueForKey...]自定義訪問?

號實施array accessors。當您撥打這些電話時,KVO會自動發佈適當的通知。所以你所要做的就是:

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

和正確的事情會自動發生。

爲方便起見,您可以編寫一個addTheArrayObject:訪問器。這個訪問將要求上述實際陣列存取之一:

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

(你可以而且應該填寫的數組中的對象適當的類,代替NSObject

然後,而不是的[myObject insertObject:…],你寫[myObject addTheArrayObject:newObject]

不幸的是,add<Key>Object:和它的對應remove<Key>Object:是,最後我檢查,只有KVO認可設置(如在NSSet)屬性,而不是數組屬性,所以你沒有得到免費的KVO通知,除非你實現它們它承認的訪問者的頂部。我提出了一個關於這個問題的bug:x-radar://問題/ 6407437

我在我的博客上有a list of all the accessor selector formats

+0

偉大的建議,謝謝。 – 2008-11-20 04:39:06

+5

#1的問題是,當你添加觀察者時,你正在觀察某個對象的屬性。數組是該屬性的*值*,而不是屬性本身。這就是爲什麼你需要使用訪問器或-mutableArrayValueForKey:來修改數組。 – 2008-11-20 06:45:54

+0

您的最後一點似乎已經過時 - 我在NSArray屬性上獲得免費的KVO通知,前提是我實現了添加和刪除訪問器。 – Bryan 2015-03-20 23:22:03

0

您需要將您的addObject:呼叫打在willChangeValueForKey:didChangeValueForKey:呼叫中。據我所知,有沒有辦法讓NSMutableArray裏你modifiying瞭解一下其主人的任何觀察員。

9

在這種情況下,我不會使用willChangeValueForKeydidChangeValueForKey。首先,它們旨在表明在這條道路上的價值已經發生了變化,而不是一對多關係中的價值正在發生變化。你會想用willChange:valuesAtIndexes:forKey:相反,如果你這樣做了。即使如此,使用像這樣的手動KVO通知也是不好的封裝。這樣做的一種更好的方式是在實際擁有陣列,其中將包括手動KVO通知的類中定義的方法addSomeObject:。這樣一來,那些將對象添加到陣列外部方法並不需要擔心處理陣列所有者的志願爲好,這不會是很直觀的,如果你開始將對象添加到可能會導致不必要的代碼,並可能錯誤陣列從幾個地方。

在這個例子中,我實際上會繼續使用mutableArrayValueForKey:。我還不能肯定與可變數組,但我從閱讀,這種方法實際上替換爲一個新的對象,整個陣列的文件認爲,如果性能是一個問題,你還需要實現擁有類insertObject:in<Key>AtIndex:removeObjectFrom<Key>AtIndex:陣列。

3

你自己對自己問題的回答幾乎是正確的。不要在外面售賣theArray。相反,聲明一個不同的屬性,theMutableArray,對應於無實例變量,和寫此訪問:

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

其結果是,其它的目的可以使用thisObject.theMutableArray進行更改到陣列,而這些變化引發KVO。

其他答案指出,如果您也執行insertObject:inTheArrayAtIndex:removeObjectFromTheArrayAtIndex:仍然正確,效率會增加。但是不需要其他物體必須知道這些或直接打電話給他們。

+0

使用此快捷方式時,我只能觀察關鍵路徑「theArray」上的更改,而不是「theMutableArray」。 – 2012-06-08 18:56:25

0

一個解決方案是使用一個NSArray並通過插入和移除從頭創建它,就像

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

比你的志願,並能夠比較新舊陣列

注:self.myArray不應該是零,否則arrayByAddingObject:結果爲零

取決於具體情況,這可能是解決方案,因爲NSArray只存儲指針,這不是一個開銷,除非您使用大型數組並且頻繁的操作

5

當你只是想觀察計數改變,你可以使用聚合關鍵路徑:

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

但要知道,在任何theArray重新排序將不會觸發。

2

如果你不需要它具有類似的性能(在我的測試相同growth rate)和更少的樣板二傳手你也可以用下面的簡單的形式。

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

這工作,因爲如果數組訪問器不落實,沒有setter方法爲重點,mutableArrayValueForKey:將尋找一個名爲_<key><key>實例變量。如果它找到一個,代理將把所有的消息轉發給這個對象。

請參閱these Apple docs,「有序集合的訪問者搜索模式」部分,#3。