KVO

引言

鍵值觀察(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ā)生變化,例如余額和利率育瓜。

圖1-1

如果這些屬性是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è)觀察到的鍵路徑荒辕,將其自身命名為觀察者汗销。

圖1-2

為了從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>

圖1-3

最后削茁,當(dāng)Person不再需要通知時(shí),并且在它還沒被釋放之前掉房,Person實(shí)例必須通過向Account實(shí)例發(fā)送removeObserver:forKeyPath:消息來取消注冊(cè)付材。

圖1-4

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è)包裝了YESNSNumber對(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)更有效的通知解析。以下代碼顯示了以這種方式選擇的balanceinterestRate屬性的示例上下文:

static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext

以下代碼演示了Person實(shí)例如何使用給定的上下文指針將自身注冊(cè)為Account實(shí)例的balanceinterestRate屬性的觀察者耀怜。

- (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條目分別通過返回NSKeyValueChangeInsertionNSKeyValueChangeRemoval或者NSKeyValueChangeReplacement來表明關(guān)系中的對(duì)象是否是被插入犀勒、移除或者替換的屎飘。

變更字典的NSKeyValueChangeIndexesKey條目是一個(gè)指定關(guān)系中已更改的索引的NSIndexSet對(duì)象。如果在注冊(cè)觀察者時(shí)贾费,將NSKeyValueObservingOptionNew或者NSKeyValueObservingOptionOld指定為選項(xiàng)钦购,變更字典中的NSKeyValueChangeOldKeyNSKeyValueChangeNewKey條目是一個(gè)包含更改之前和之后的相關(guān)對(duì)象的值的數(shù)組。

以下示例顯示了Person觀察者的用于記錄屬性balanceinterestRate的舊值和新值的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ì)象先馆、鍵路徑和上下文來移除觀察者。 以下示例顯示了移除balanceinterestRate的觀察者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è)NSRangeExceptionremoveObserver: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)通知胃夏。更典型的情況是,我們可能想要完全控制特定屬性的通知昌跌。在這種情況下仰禀,需要覆蓋NSObjectautomaticallyNotifiesObserversForKey:的實(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)firstNamelastName屬性發(fā)生更改時(shí)剃允,必須通知觀察fullName屬性的對(duì)象,因?yàn)樗鼈儠?huì)影響屬性的值。

一種解決方案是重寫keyPathsForValuesAffectingValueForKey:方法來指定PersonfullName屬性依賴于firstNamelastName屬性斥废。以下示例顯示了這種依賴的實(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作為鍵返回的。

在這種情況下症见,有兩種可能的解決方案:

  1. 使用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;
}
  1. 如果正在使用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í)例的類兔辅。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市击喂,隨后出現(xiàn)的幾起案子维苔,更是在濱河造成了極大的恐慌,老刑警劉巖懂昂,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件介时,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡凌彬,警方通過查閱死者的電腦和手機(jī)沸柔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铲敛,“玉大人褐澎,你說我怎么就攤上這事》ソ” “怎么了工三?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)先鱼。 經(jīng)常有香客問我俭正,道長(zhǎng),這世上最難降的妖魔是什么焙畔? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任掸读,我火速辦了婚禮,結(jié)果婚禮上闹蒜,老公的妹妹穿的比我還像新娘寺枉。我一直安慰自己,他們只是感情好绷落,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布姥闪。 她就那樣靜靜地躺著,像睡著了一般砌烁。 火紅的嫁衣襯著肌膚如雪筐喳。 梳的紋絲不亂的頭發(fā)上催式,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音避归,去河邊找鬼荣月。 笑死,一個(gè)胖子當(dāng)著我的面吹牛梳毙,可吹牛的內(nèi)容都是我干的哺窄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼账锹,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼萌业!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起奸柬,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤生年,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后廓奕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抱婉,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年桌粉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蒸绩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡番甩,死狀恐怖侵贵,靈堂內(nèi)的尸體忽然破棺而出届搁,到底是詐尸還是另有隱情缘薛,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布卡睦,位于F島的核電站宴胧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏表锻。R本人自食惡果不足惜恕齐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞬逊。 院中可真熱鬧显歧,春花似錦、人聲如沸确镊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蕾域。三九已至拷肌,卻和暖如春到旦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背巨缘。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工添忘, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人若锁。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓搁骑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親又固。 傳聞我的和親對(duì)象是個(gè)殘疾皇子靶病,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

  • 關(guān)于鍵值編碼 鍵值編碼(KVC)是一種由NSKeyValueCoding非正式協(xié)議提供的機(jī)制,對(duì)象采用該機(jī)制來提供...
    漸z閱讀 931評(píng)論 0 0
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,395評(píng)論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,105評(píng)論 1 32
  • 開始 關(guān)于鍵值編碼 鍵值編碼是一種機(jī)制口予,通過NSKeyValueCoding非正式協(xié)議,對(duì)象采用這種機(jī)制提供對(duì)其屬...
    影痕殘碎閱讀 1,207評(píng)論 0 2
  • 不曾歷盡艱難的人娄周,不會(huì)懂得走出沙漠的人,見到一杯清茶的幸福感沪停;沒有被風(fēng)雨侵襲過的花朵煤辨,不會(huì)感受到在歲月的枝頭迎著陽...
    巧手精品手工包閱讀 139評(píng)論 0 0