引言
鍵值觀察(KVO)提供了一種機(jī)制以允許對(duì)象被告知其他對(duì)象的特定屬性的更改剔交,它對(duì)應(yīng)用程序中的模型和控制器層之間的通信特別有用敬飒。在OS X中勋眯,控制器層綁定技術(shù)嚴(yán)重依賴于鍵值觀察值朋【谥桑控制器對(duì)象通常觀察模型對(duì)象的屬性洛波,視圖對(duì)象通過控制器觀察模型對(duì)象的屬性胰舆。 此外,模型對(duì)象還可以觀察其他模型對(duì)象(通常用于確定依賴的值何時(shí)改變)蹬挤,甚至其自身(再次確定依賴的值何時(shí)改變)缚窿。
可以觀察 simple attributes、to-one relationships 和 to-many relationships 這三種類型的屬性(關(guān)于屬性類型的描述焰扳,請(qǐng)參看KVC)倦零。to-many relationships 屬性的觀察者被告知所做的更改的類型以及更改涉及哪些對(duì)象。
一個(gè)簡(jiǎn)單的例子說明了KVO如何在應(yīng)用程序中發(fā)揮作用的吨悍。假設(shè)一個(gè)Person
對(duì)象和一個(gè)Account
對(duì)象代表某個(gè)人在銀行的儲(chǔ)蓄賬戶扫茅,那么Person
實(shí)例可能需要知道Account
實(shí)例的某些詳情合適發(fā)生變化,例如余額和利率育瓜。
如果這些屬性是Account
的公開屬性诞帐,那么Person
可以定期輪詢Account
以發(fā)現(xiàn)變化。但這種做法的效率非常低爆雹,并且通常是不切實(shí)際的停蕉。更好的做法是使用KVO,這類似于在發(fā)生更改時(shí)钙态,Person
接收一個(gè)中斷慧起。
要使用KVO,首先必須確保被觀察的對(duì)象兼容KVO册倒。通常情況下蚓挤,如果對(duì)象繼承自NSObject
并且以常規(guī)方式創(chuàng)建屬性,對(duì)象及其屬性將自動(dòng)兼容KVO驻子。還可以手動(dòng)實(shí)現(xiàn)KVO兼容灿意。KVO兼容描述了自動(dòng)和手動(dòng)鍵值觀察之間的區(qū)別,以及如何實(shí)現(xiàn)它們崇呵。
接下來缤剧,必須注冊(cè)觀察者Person
實(shí)例和被觀察的Account
實(shí)例。Person
發(fā)送一個(gè)addObserver:forKeyPath:options:context:
消息給Account
域慷,對(duì)于每個(gè)觀察到的鍵路徑荒辕,將其自身命名為觀察者汗销。
為了從Account
接收更改通知,Person
實(shí)現(xiàn)了所有觀察者都需要的observeValueForKeyPath:ofObject:change:context:
方法抵窒。只要注冊(cè)的鍵路徑的其中一個(gè)發(fā)生變化弛针,Account
就會(huì)發(fā)送observeValueForKeyPath:ofObject:change:context:
消息給Person
。然后李皇,Person
能夠基于更改通知采取適當(dāng)?shù)拇胧?/p>
最后削茁,當(dāng)Person
不再需要通知時(shí),并且在它還沒被釋放之前掉房,Person
實(shí)例必須通過向Account
實(shí)例發(fā)送removeObserver:forKeyPath:
消息來取消注冊(cè)付材。
KVO的第一益處是不必在每次屬性更改時(shí)都實(shí)施自己的方案來發(fā)送通知。其定義良好的基礎(chǔ)架構(gòu)具有框架級(jí)別的支持圃阳,使得其易于使用——通常不必向項(xiàng)目添加任何代碼厌衔。此外,基礎(chǔ)架構(gòu)的功能已經(jīng)齊全捍岳,這使得單個(gè)屬性以及依賴的值支持多個(gè)觀察者變得容易富寿。
與使用NSNotificationCenter
的通知不同,沒有中心對(duì)象為所有觀察者提供更改通知锣夹。 取而代之的是页徐,在進(jìn)行更改時(shí)將通知直接發(fā)送到觀察對(duì)象。NSObject
提供了鍵值觀察的基本實(shí)現(xiàn)银萍,很少需要覆蓋這些方法变勇。
注冊(cè)KVO
必須執(zhí)行以下步驟來使對(duì)象能夠接收鍵值觀察通知:
- 使用
addObserver:forKeyPath:options:context:
方法將觀察者注冊(cè)到被觀察對(duì)象。 - 在觀察者內(nèi)部實(shí)現(xiàn)
observeValueForKeyPath:ofObject:change:context:
方法來接收更改通知消息贴唇。 - 當(dāng)觀察者不需要在接收更改通知消息時(shí)搀绣,使用
removeObserver:forKeyPath:
方法取消注冊(cè)觀察者。
重要:并非所有類的所有屬性都是兼容KVO的戳气×椿迹可以按照KVO兼容中描述的步驟來確保自己的類是兼容KVO的。
注冊(cè)為觀察者
觀察者對(duì)象首先通過發(fā)送一個(gè)addObserver:forKeyPath:options:context:
消息并傳遞其本身以及被觀察的屬性的鍵路徑瓶您,來向被觀察對(duì)象注冊(cè)自己麻捻。觀察者還指定了一個(gè)options
參數(shù)和一個(gè)上下文指針來管理通知。
Options
選項(xiàng)參數(shù)會(huì)影響通知中提供的變更字典的內(nèi)容以及生成通知的方式呀袱。
觀察者對(duì)象可以使用選項(xiàng)NSKeyValueObservingOptionOld
來接收被觀察屬性被更改之前的值贸毕,以及使用選項(xiàng)NSKeyValueObservingOptionNew
請(qǐng)求屬性的新值。還可以使用按位OR
組合這些選項(xiàng)夜赵,以便同時(shí)接收新值和舊值明棍。
選項(xiàng)NSKeyValueObservingOptionInitial
指示被觀察的對(duì)象在addObserver:forKeyPath:options:context:
方法返回之前發(fā)送一個(gè)立即更改通知,可以使用此額外的一次性通知來在觀察者對(duì)象中設(shè)置屬性的初始值油吭。
選項(xiàng)NSKeyValueObservingOptionPrior
指示被觀察對(duì)象在屬性更改之前發(fā)送一個(gè)通知击蹲。如果通知提供的變更字典中包含一個(gè)鍵NSKeyValueChangeNotificationIsPriorKey
,且鍵對(duì)應(yīng)的值為一個(gè)包裝了YES
的NSNumber
對(duì)象婉宰,則表示這是一個(gè)預(yù)更改通知歌豺。當(dāng)觀察者對(duì)象自己也要兼容KVO并且需要調(diào)用依賴于其他對(duì)象的被觀察屬性的屬性的一個(gè)willChange...
方法時(shí),可以使用此預(yù)更改通知心包。尋常的已更改通知生成得太晚类咧,會(huì)導(dǎo)致無法及時(shí)調(diào)用willChange...
方法。
Context
addObserver:forKeyPath:options:context:
消息中的上下文指針包含將在相應(yīng)的更改通知中回傳給觀察者對(duì)象的任意數(shù)據(jù)蟹腾『弁铮可以指定該參數(shù)為NULL
并完全依賴于鍵路徑字符串來確定更改通知的接收者,但是這種方式可能會(huì)在觀察者對(duì)象的父類因?yàn)椴煌脑蛞灿^察相同的鍵路徑時(shí)出現(xiàn)問題娃殖。
更安全和更可擴(kuò)展的方法是使用上下文來確保收到的通知是發(fā)送給觀察者對(duì)象的值戳,而不是發(fā)送給觀察者對(duì)象的父類。
在類中唯一命名的靜態(tài)變量的地址是一個(gè)很好的上下文炉爆,并且在父類或者子類中以相同方式選擇的上下文不會(huì)重疊堕虹。可以為整個(gè)類選擇單獨(dú)一個(gè)上下文芬首,并依賴通知消息中的鍵路徑字符串來確定更改的內(nèi)容赴捞。或者郁稍,可以為每個(gè)被觀察的鍵路徑創(chuàng)建不同的上下文來完全繞過字符串比較的需要赦政,從而實(shí)現(xiàn)更有效的通知解析。以下代碼顯示了以這種方式選擇的balance
和interestRate
屬性的示例上下文:
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext
以下代碼演示了Person
實(shí)例如何使用給定的上下文指針將自身注冊(cè)為Account
實(shí)例的balance
和interestRate
屬性的觀察者耀怜。
- (void)registerAsObserverForAccount:(Account*)account
{
[account addObserver:self forKeyPath:@"balance" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:PersonAccountBalanceContext];
[account addObserver:self forKeyPath:@"interestRate" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:PersonAccountInterestRateContext];
}
注意:鍵值觀察方法
addObserver:forKeyPath:options:context:
不會(huì)保留對(duì)觀察者對(duì)象恢着、被觀察對(duì)象或者上下文的強(qiáng)引用,我們應(yīng)該自己在代碼中確保在必要時(shí)保留對(duì)觀察者對(duì)象财破、被觀察對(duì)象或者上下文的強(qiáng)引用然评。
接收更改通知
當(dāng)對(duì)象的一個(gè)被觀察的屬性的值改變時(shí),觀察者對(duì)象會(huì)收到一個(gè)observeValueForKeyPath:ofObject:change:context:
消息狈究。所有的觀察者對(duì)象都必須實(shí)現(xiàn)這個(gè)方法碗淌。
觀察者對(duì)象提供了觸發(fā)通知的鍵路徑、作為相關(guān)對(duì)象的自身抖锥、包含有關(guān)更改的詳細(xì)信息的字典以及觀察者為鍵路徑注冊(cè)時(shí)提供的上下文指針亿眠。
變更字典中的條目NSKeyValueChangeKindKey
提供了與所發(fā)生更改的類型有關(guān)的信息。如果被觀察對(duì)象的值已經(jīng)改變磅废,NSKeyValueChangeKindKey
條目會(huì)返回NSKeyValueChangeSetting
纳像。根據(jù)注冊(cè)觀察者時(shí)指定的選項(xiàng),變更字典中的NSKeyValueChangeOldKey
條目和NSKeyValueChangeNewKey
條目包含更改之前和之后的屬性值拯勉。如果屬性是一個(gè)對(duì)象竟趾,則直接提供該值憔购。如果屬性是一個(gè)標(biāo)量或者結(jié)構(gòu)體,該值會(huì)被包裝在NSValue
對(duì)象中岔帽。
如果被觀察的屬性是一個(gè)to-many relationship玫鸟,則NSKeyValueChangeKindKey
條目分別通過返回NSKeyValueChangeInsertion
、NSKeyValueChangeRemoval
或者NSKeyValueChangeReplacement
來表明關(guān)系中的對(duì)象是否是被插入犀勒、移除或者替換的屎飘。
變更字典的NSKeyValueChangeIndexesKey
條目是一個(gè)指定關(guān)系中已更改的索引的NSIndexSet
對(duì)象。如果在注冊(cè)觀察者時(shí)贾费,將NSKeyValueObservingOptionNew
或者NSKeyValueObservingOptionOld
指定為選項(xiàng)钦购,變更字典中的NSKeyValueChangeOldKey
和NSKeyValueChangeNewKey
條目是一個(gè)包含更改之前和之后的相關(guān)對(duì)象的值的數(shù)組。
以下示例顯示了Person
觀察者的用于記錄屬性balance
和interestRate
的舊值和新值的observeValueForKeyPath:ofObject:change:context:
方法實(shí)現(xiàn)褂萧。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == PersonAccountBalanceContext) {
// Do something with the balance…
} else if (context == PersonAccountInterestRateContext) {
// Do something with the interest rate…
} else {
// Any unrecognized context must belong to super
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
在注冊(cè)觀察者時(shí)押桃,如果指定一個(gè)NULL
作為上下文,則將通知的鍵路徑與正在觀察的鍵路徑相比較來確定更改的內(nèi)容导犹。如果對(duì)所有被觀察的鍵路徑使用單獨(dú)一個(gè)上下文怨规,則首先將通知的上下文與其相比較,找到匹配項(xiàng)后再使用鍵路徑字符串比較來確定具體更改的內(nèi)容锡足。如果為每個(gè)鍵路徑提供唯一的上下文波丰,如上所示,一系列簡(jiǎn)單的指針比較會(huì)同時(shí)告訴我們通知是否適用于此觀察者以及如果是舶得,則哪個(gè)鍵路徑已經(jīng)更改了掰烟。
在任何情況下,當(dāng)觀察者不能確定更改通知是否適用于自己(不能識(shí)別上下文或者鍵路徑)時(shí)沐批,觀察者應(yīng)該總是調(diào)用其父類的observeValueForKeyPath:ofObject:change:context:
實(shí)現(xiàn)纫骑。
注意:如果通知傳遞到類層次結(jié)構(gòu)的頂部,則
NSObject
會(huì)拋出一個(gè)NSInternalInconsistencyException
九孩。
移除觀察者
通過向被觀察對(duì)象發(fā)送一個(gè)removeObserver:forKeyPath:context:
消息并指定觀察者對(duì)象先馆、鍵路徑和上下文來移除觀察者。 以下示例顯示了移除balance
和interestRate
的觀察者Person
躺彬。
- (void)unregisterAsObserverForAccount:(Account*)account
{
[account removeObserver:self forKeyPath:@"balance" context:PersonAccountBalanceContext];
[account removeObserver:self forKeyPath:@"interestRate" context:PersonAccountInterestRateContext];
}
收到removeObserver:forKeyPath:context:
消息后煤墙,觀察者對(duì)象將不會(huì)接收到指定的鍵路徑和對(duì)象的任何observeValueForKeyPath:ofObject:change:context:
消息。
移除觀察者時(shí)宪拥,請(qǐng)記住以下兩點(diǎn):
- 如果請(qǐng)求移除一個(gè)還未被注冊(cè)的觀察者仿野,則會(huì)導(dǎo)致一個(gè)
NSRangeException
。removeObserver:forKeyPath:context:
和addObserver:forKeyPath:options:context:
方法的調(diào)用應(yīng)該相對(duì)應(yīng)她君,或者如果在應(yīng)用程序中無法這樣做脚作,則將removeObserver:forKeyPath:context:
調(diào)用放在try/catch
的block中,以便處理潛在異常。 - 觀察者對(duì)象被釋放時(shí)球涛,不會(huì)移除其自身來取消注冊(cè)觀察者劣针。被觀察對(duì)象會(huì)繼續(xù)發(fā)送通知,無視觀察者的狀態(tài)亿扁。但是捺典,變更通知與其他任何消息一樣,發(fā)送給一個(gè)已經(jīng)釋放的對(duì)象魏烫,會(huì)觸發(fā)一個(gè)內(nèi)存訪問異常辣苏。因此肝箱,必須確保觀察者在從內(nèi)存中消失之前將其自身移除哄褒。
KVO兼容
為了讓特定的屬性兼容KVO,一個(gè)類必須確保以下內(nèi)容:
- 該類的屬性必須是兼容KVC的煌张。KVO支持的數(shù)據(jù)類型與KVC的相同呐赡,包括Objective-C對(duì)象、標(biāo)量和結(jié)構(gòu)體骏融。
- 該類會(huì)為屬性發(fā)出KVO更改通知链嘀。
- 依賴的鍵已被正確注冊(cè)(請(qǐng)參看注冊(cè)依賴的鍵)。
有兩種技術(shù)可確保發(fā)出通知档玻。默認(rèn)情況下怀泊,NSObject
類為一個(gè)類的兼容KVC的所有屬性提供自動(dòng)支持。通常误趴,如果我們遵循標(biāo)準(zhǔn)的Cocoa編碼和命名約定霹琼,則可以使用自動(dòng)更改通知——不必編寫任何其他代碼。
手動(dòng)更改通知提供了對(duì)何時(shí)發(fā)出通知的額外控制凉当,但需要為其編寫額外的代碼枣申。可以通過實(shí)現(xiàn)類方法automaticallyNotifiesObserversForKey:
來控制自類屬性的自動(dòng)通知看杭。
自動(dòng)更改通知
NSObject
提供了自動(dòng)鍵值更改通知的基本實(shí)現(xiàn)忠藤。自動(dòng)鍵值更改通知告知觀察者使用鍵值兼容的訪問器和鍵值編碼方法所做的更改。mutableArrayValueForKey:
楼雹、mutableOrderedSetValueForKey:
和mutableSetValueForKey:
方法返回的集合代理對(duì)象也是支持自動(dòng)通知的延刘。
以下示例代碼會(huì)讓屬性的觀察者被告知屬性的更改。
// Call the accessor method.
[account setName:@"Savings"];
// Use setValue:forKey:.
[account setValue:@"Savings" forKey:@"name"];
// Use a key path, where 'account' is a kvc-compliant property of 'document'.
[document setValue:@"Savings" forKeyPath:@"account.name"];
// Use mutableArrayValueForKey: to retrieve a relationship proxy object.
Transaction *newTransaction = [[Transaction alloc] init];
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];
手動(dòng)更改通知
在某些情況下浸锨,可能想要控制通知過程胆敞,例如,最大程度地減少觸發(fā)那些對(duì)于應(yīng)用程序特定原因而言是不必要的通知携悯,或者將大量更改合并到單個(gè)通知中祭芦。手動(dòng)更改通知提供執(zhí)行這些操作的方法。
手動(dòng)和自動(dòng)通知不是互斥的憔鬼。除了現(xiàn)有的自動(dòng)通知之外龟劲,還可以自由發(fā)出手動(dòng)通知胃夏。更典型的情況是,我們可能想要完全控制特定屬性的通知昌跌。在這種情況下仰禀,需要覆蓋NSObject
的automaticallyNotifiesObserversForKey:
的實(shí)現(xiàn)。對(duì)于想要避免自動(dòng)通知的屬性蚕愤,子類的automaticallyNotifiesObserversForKey:
實(shí)現(xiàn)應(yīng)返回NO
答恶。子類的實(shí)現(xiàn)還應(yīng)該為任何無法識(shí)別的鍵調(diào)用super
。以下示例啟用了balance
屬性的手動(dòng)通知萍诱,并允許父類確定所有其他鍵的通知悬嗓。
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey
{
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
為了實(shí)現(xiàn)手動(dòng)觀察者通知,請(qǐng)?jiān)诟闹抵罢{(diào)用willChangeValueForKey:
方法和在更改值之后調(diào)用didChangeValueForKey:
方法裕坊。以下示例實(shí)現(xiàn)了balance
屬性的手動(dòng)通知包竹。
- (void)setBalance:(double)theBalance
{
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
可以通過首先檢查值是否已更改來最大限度地減少發(fā)送不必要的通知。以下示例驗(yàn)證了balance
的值籍凝,只提供了更改后的通知周瞎。
- (void)setBalance:(double)theBalance
{
if (theBalance != _balance)
{
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
}
如果單個(gè)操作導(dǎo)致多個(gè)鍵發(fā)生更改,則必須嵌套更改通知饵蒂,如下所示声诸。
- (void)setBalance:(double)theBalance
{
[self willChangeValueForKey:@"balance"];
[self willChangeValueForKey:@"itemChanged"];
_balance = theBalance;
_itemChanged = _itemChanged+1;
[self didChangeValueForKey:@"itemChanged"];
[self didChangeValueForKey:@"balance"];
}
在一個(gè)有序的 to-many relationship 的情況下,不僅必須指定更改的鍵退盯,還必須指定更改的類型和所涉及對(duì)象的索引彼乌。更改的類型是一個(gè)NSKeyValueChange
,它的值可以為NSKeyValueChangeInsertion
得问、NSKeyValueChangeRemoval
或者NSKeyValueChangeReplacement
囤攀。受影響對(duì)象的索引包含在NSIndexSet
對(duì)象中傳遞。
- (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes
{
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@"transactions"];
// Remove the transaction objects at the specified indexes.
[self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@"transactions"];
}
注冊(cè)依賴的鍵
在許多情況下宫纬,一個(gè)屬性的值取決于另一個(gè)對(duì)象中的一個(gè)或者多個(gè)其他屬性的值焚挠。如果一個(gè)屬性的值發(fā)生更改,那么派生屬性的值也應(yīng)該被標(biāo)記為更改漓骚。如何確保為這些依賴的屬性發(fā)送鍵值觀察通知取決于關(guān)系的基數(shù)蝌衔。
To-One Relationships
要為一個(gè) to-one relationship 自動(dòng)觸發(fā)通知,需要重寫keyPathsForValuesAffectingValueForKey:
方法或者實(shí)現(xiàn)一個(gè)合適的方法蝌蹂,該方法遵循它為注冊(cè)依賴的鍵定義的模式噩斟。
例如,一個(gè)人的全名取決于名字和姓氏孤个。返回全名的方法可以寫成如下:
- (NSString *)fullName
{
return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
}
當(dāng)firstName
和lastName
屬性發(fā)生更改時(shí)剃允,必須通知觀察fullName
屬性的對(duì)象,因?yàn)樗鼈儠?huì)影響屬性的值。
一種解決方案是重寫keyPathsForValuesAffectingValueForKey:
方法來指定Person
的fullName
屬性依賴于firstName
和lastName
屬性斥废。以下示例顯示了這種依賴的實(shí)現(xiàn):
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key
{
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"fullName"])
{
NSArray *affectingKeys = @[@"lastName", @"firstName"];
keyPaths = [keyPaths setByAddingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
在自定義實(shí)現(xiàn)中通常應(yīng)該調(diào)用super
并返回一個(gè)集合椒楣,以免干擾父類中的此方法的實(shí)現(xiàn)。
還可以通過實(shí)現(xiàn)一個(gè)遵循命名約定keyPathsForValuesAffecting<Key>
的類方法來實(shí)現(xiàn)相同的結(jié)果牡肉,其中<Key>
是依賴于值的屬性的名稱(首字母大寫)捧灰。之前示例中的代碼可以使用這種格式編寫成一個(gè)名為keyPathsForValuesAffectingFullName
的類方法。
+ (NSSet *)keyPathsForValuesAffectingFullName
{
return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}
當(dāng)使用類別往現(xiàn)有類中添加一個(gè)計(jì)算屬性時(shí)统锤,不能覆蓋keyPathsForValuesAffectingValueForKey:
方法毛俏,因?yàn)椴恢С衷陬悇e中覆蓋方法。在這種情況下饲窿,實(shí)現(xiàn)一個(gè)keyPathsForValuesAffecting<Key>
類方法來使用此機(jī)制煌寇。
注意:無法通過實(shí)現(xiàn)
keyPathsForValuesAffectingValueForKey:
方法來配置 to-many relationships 的依賴關(guān)系。取而代之的是免绿,必須觀察 to-many relationships 中每個(gè)對(duì)象的相應(yīng)屬性唧席,并通過自己更新依賴的鍵來響應(yīng)其值的更改擦盾。
To-Many Relationships
keyPathsForValuesAffectingValueForKey:
方法不支持包含一個(gè) to-many relationship 的鍵路徑嘲驾。例如,假設(shè)存在一個(gè)Department
對(duì)象迹卢,該對(duì)象有一個(gè)與Employee
(員工)對(duì)象具有 to-many relationship 的employees
集合屬性辽故,Employee
對(duì)象有一個(gè)salary
(薪水)屬性。我們可能希望Department
(部門)對(duì)象有一個(gè)totalSalary
(薪水總額)屬性腐碱,該屬性的值取決于employees
集合中所有Employee
對(duì)象的salary
誊垢。這種情況是無法使用keyPathsForValuesAffectingTotalSalary
方法并將employees.salary
作為鍵返回的。
在這種情況下症见,有兩種可能的解決方案:
- 使用KVO將父項(xiàng)(在此示例中為
Department
)注冊(cè)為所有子項(xiàng)(本示例中的Employee
)的相關(guān)屬性(salary
屬性)的觀察者喂走。在employees
集合中添加和刪除Employee
子對(duì)象時(shí),必須注冊(cè)和取消注冊(cè)Department
父對(duì)象作為觀察者谋作。在observeValueForKeyPath:ofObject:change:context:
方法中芋肠,更新依賴值來響應(yīng)更改,如下所示:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == totalSalaryContext)
{
[self updateTotalSalary];
}
else
// deal with other observations and/or invoke super...
}
- (void)updateTotalSalary
{
[self setTotalSalary:[self valueForKeyPath:@"employees.@sum.salary"]];
}
- (void)setTotalSalary:(NSNumber *)newTotalSalary
{
if (totalSalary != newTotalSalary)
{
[self willChangeValueForKey:@"totalSalary"];
_totalSalary = newTotalSalary;
[self didChangeValueForKey:@"totalSalary"];
}
}
- (NSNumber *)totalSalary
{
return _totalSalary;
}
- 如果正在使用Core Data遵蚜,則可以將父項(xiàng)作為其 managed object context 的觀察者注冊(cè)到應(yīng)用程序的通知中心帖池。父項(xiàng)應(yīng)該以類似于鍵值觀察的方式響應(yīng)子項(xiàng)們發(fā)出的相關(guān)變更通知。
KVO實(shí)現(xiàn)細(xì)節(jié)
自動(dòng)鍵值觀察是使用 isa-swizzling 技術(shù)實(shí)現(xiàn)的吭净。
isa
指針指向?qū)ο蟮念愃冢惥S護(hù)著一個(gè)調(diào)度表,該調(diào)度表基本上包含指向該類實(shí)現(xiàn)的方法的指針以及其他數(shù)據(jù)寂殉。
當(dāng)為對(duì)象的一個(gè)屬性注冊(cè)觀察者時(shí)囚巴,被觀察對(duì)象的isa
指針被修改并指向一個(gè)中間類而不是真正的類,這樣被觀察對(duì)象實(shí)際上就成為了此中間類的一個(gè)實(shí)例。這個(gè)中間類繼承自被觀察對(duì)象原本的類彤叉,其重寫了被觀察屬性的setter以便在被觀察屬性的值改變時(shí)發(fā)出更改通知晤斩。同時(shí),它還重寫了class
方法并返回原本的類姆坚。
因此澳泵,絕不應(yīng)該依賴isa
指針來判斷類成員資格。相反兼呵,應(yīng)該使用class
方法來判斷對(duì)象實(shí)例的類兔辅。