?? ? 鍵值觀察是一種機(jī)制,允許將其他對(duì)象的指定屬性的更改通知對(duì)象。
?? ? 重要:為了理解鍵值觀察,您必須首先理解鍵值編碼。
?? ? 鍵值觀察提供了一種機(jī)制,允許將其他對(duì)象的特定屬性的更改通知對(duì)象。它對(duì)于應(yīng)用程序中模型層和控制器層之間的通信特別有用。(在OS X中,控制器層綁定技術(shù)嚴(yán)重依賴(lài)于鍵值觀察。)控制器對(duì)象通常觀察模型對(duì)象的屬性,視圖對(duì)象通過(guò)控制器觀察模型對(duì)象的屬性。此外,模型對(duì)象可以觀察其他模型對(duì)象(通常用于確定依賴(lài)值何時(shí)發(fā)生變化),甚至可以觀察自身(再次用于確定依賴(lài)值何時(shí)發(fā)生變化)。
?? ? 您可以觀察屬性,包括簡(jiǎn)單屬性、一對(duì)一關(guān)系和多對(duì)多關(guān)系。許多關(guān)系的觀察者被告知所做變更的類(lèi)型以及變更中涉及到的對(duì)象。
?? ? 一個(gè)簡(jiǎn)單的示例演示了KVO如何在應(yīng)用程序中發(fā)揮作用。假設(shè)Person對(duì)象與Account對(duì)象交互,表示該對(duì)象在銀行的儲(chǔ)蓄帳戶(hù)。Person的實(shí)例可能需要知道Account實(shí)例的某些方面何時(shí)發(fā)生變化,比如余額或利率。
?? ? 如果這些屬性是Account的公共屬性,那么用戶(hù)可以定期輪詢(xún)帳戶(hù)以發(fā)現(xiàn)更改,但這當(dāng)然是低效的,而且通常不切實(shí)際。更好的方法是使用KVO,它類(lèi)似于在發(fā)生更改時(shí)接收中斷的人。
?? ? 要使用KVO,首先必須確保所觀察的對(duì)象(本例中的帳戶(hù))符合KVO。通常,如果您的對(duì)象繼承自NSObject,并且您以通常的方式創(chuàng)建屬性,那么您的對(duì)象及其屬性將自動(dòng)與KVO兼容。手動(dòng)實(shí)現(xiàn)遵從性也是可能的。KVO遵從性描述了自動(dòng)和手動(dòng)鍵值觀察之間的區(qū)別,以及如何實(shí)現(xiàn)這兩者。
?? ? 接下來(lái),您必須注冊(cè)您的觀察者實(shí)例(Person)和觀察到的實(shí)例(Account)。Person向帳戶(hù)發(fā)送一個(gè)addObserver:forKeyPath:options:context: message,每個(gè)觀察到的密鑰路徑發(fā)送一次,并將其命名為observer。
?? ? 為了從帳戶(hù)接收更改通知,Person實(shí)現(xiàn)了所有觀察者都需要的observeValueForKeyPath:ofObject:change:context: method。一旦注冊(cè)的密鑰路徑發(fā)生更改,該帳戶(hù)將向該人發(fā)送此消息。然后,該人可以根據(jù)變更通知采取適當(dāng)?shù)男袆?dòng)。
?? ? 最后,當(dāng)不再需要通知時(shí)(至少在解除分配之前),Person實(shí)例必須通過(guò)發(fā)送消息removeObserver:forKeyPath:到帳戶(hù)來(lái)注銷(xiāo)注冊(cè)。
?? ? 注冊(cè)鍵值觀察描述了注冊(cè)、接收和注銷(xiāo)鍵值觀察通知的完整生命周期。
?? ? KVO的主要好處是,您不必實(shí)現(xiàn)自己的方案來(lái)在每次屬性更改時(shí)發(fā)送通知。它定義良好的基礎(chǔ)架構(gòu)具有框架級(jí)支持,這使得它易于采用——通常您不需要向項(xiàng)目中添加任何代碼。此外,基礎(chǔ)設(shè)施已經(jīng)具備了完整的功能,這使得支持單個(gè)屬性的多個(gè)觀察者和依賴(lài)值變得很容易。
?? ? 注冊(cè)依賴(lài)鍵說(shuō)明如何指定一個(gè)鍵的值依賴(lài)于另一個(gè)鍵的值。
?? ? 與使用NSNotificationCenter的通知不同,沒(méi)有為所有觀察者提供更改通知的中心對(duì)象。相反,當(dāng)發(fā)生更改時(shí),通知將直接發(fā)送到觀察對(duì)象。NSObject提供了鍵值觀察的基本實(shí)現(xiàn),您應(yīng)該很少需要覆蓋這些方法。
?? ? 注冊(cè)鍵值觀察
?? ? 您必須執(zhí)行以下步驟,以使對(duì)象能夠接收符合kvo的屬性的鍵值觀察通知:
?? ? 1.使用addObserver:forKeyPath:options:context:方法向被觀察對(duì)象注冊(cè)觀察者。
?? ? 2.在觀察者內(nèi)部實(shí)現(xiàn)observeValueForKeyPath:ofObject:change:context:接受變更通知消息。
?? ? 3.使用removeObserver:forKeyPath方法注銷(xiāo)觀察者的注冊(cè):當(dāng)它不再應(yīng)該接收消息時(shí)。至少,在觀察者從內(nèi)存中釋放之前調(diào)用這個(gè)方法。
?? ? 重要:并不是所有的類(lèi)都與kvo兼容。通過(guò)遵循KVO遵從性中描述的步驟,您可以確保您自己的類(lèi)符合KVO。通常,蘋(píng)果提供的框架中的屬性只有在這樣的文檔中才符合kvo。
?? ? 注冊(cè)為觀察者
?? ? 觀察對(duì)象首先通過(guò)發(fā)送addObserver:forKeyPath:options:context: message向觀察對(duì)象注冊(cè)自己,并將自己作為觀察者和要觀察的屬性的鍵路徑進(jìn)行傳遞。觀察者還指定一個(gè)選項(xiàng)參數(shù)和一個(gè)上下文指針來(lái)管理通知的各個(gè)方面
?? ? 選項(xiàng)
?? ? 選項(xiàng)參數(shù)(以位或選項(xiàng)常量的形式指定)影響通知中提供的更改字典的內(nèi)容和生成通知的方式。
?? ? 通過(guò)指定選項(xiàng)NSKeyValueObservingOptionOld,您可以選擇從更改之前接收被觀察屬性的值。使用選項(xiàng)NSKeyValueObservingOptionNew請(qǐng)求屬性的新值。通過(guò)按位或這些選項(xiàng)中的一個(gè),可以同時(shí)接收新值和舊值。
?? ? 指示被觀察對(duì)象使用NSKeyValueObservingOptionInitial選項(xiàng)發(fā)送一個(gè)即時(shí)更改通知(在addObserver:forKeyPath:options:context: returns之前)。您可以使用此附加的一次性通知在觀察者中建立屬性的初始值。
?? ? 通過(guò)包含選項(xiàng)NSKeyValueObservingOptionPrior,可以指示被觀察對(duì)象在屬性更改之前(除了通常的更改之后的通知之外)發(fā)送通知。更改字典通過(guò)包含NSNumber包裝YES值的鍵NSKeyValueChangeNotificationIsPriorKey來(lái)表示更改前通知。否則該鍵不存在。當(dāng)觀察者自己的KVO遵從性要求它為依賴(lài)于被觀察屬性的屬性之一調(diào)用-willChange…方法時(shí),您可以使用prechange通知。通常的更改后通知來(lái)得太晚,無(wú)法及時(shí)調(diào)用willChange。
?? ? 上下文
?? ? 消息中addObserver:forKeyPath:options:context: message中的上下文指針包含任意數(shù)據(jù),這些數(shù)據(jù)將在相應(yīng)的更改通知中傳遞回觀察者。您可以指定NULL并完全依賴(lài)于鍵路徑字符串來(lái)確定更改通知的來(lái)源,但是這種方法可能會(huì)為其超類(lèi)由于不同原因也在觀察相同鍵路徑的對(duì)象帶來(lái)問(wèn)題。
?? ? 一種更安全、更可擴(kuò)展的方法是使用上下文來(lái)確保接收到的通知是針對(duì)觀察者而不是超類(lèi)的。
?? ? 類(lèi)中唯一命名的靜態(tài)變量的地址是一個(gè)很好的上下文。在超類(lèi)或子類(lèi)中以類(lèi)似方式選擇的上下文不太可能重疊。您可以為整個(gè)類(lèi)選擇一個(gè)上下文,并依賴(lài)于通知消息中的鍵路徑字符串來(lái)確定更改了什么。或者,您可以為每個(gè)觀察到的鍵路徑創(chuàng)建不同的上下文,這完全避免了字符串比較的需要,從而產(chǎn)生更高效的通知解析。清單1顯示了以這種方式選擇的balance和interestRate屬性的示例上下文。
?? ? 清單1創(chuàng)建上下文指針
?? ? static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
?? ? static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
?? ? 清單2中的示例演示了Person實(shí)例如何使用給定的上下文指針將自己注冊(cè)為Account實(shí)例的balance和interestRate屬性的觀察者。
?? ? 清單2將檢查器注冊(cè)為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:方法的鍵值不維護(hù)對(duì)觀察對(duì)象、被觀察對(duì)象或上下文的強(qiáng)引用。您應(yīng)該確保在必要時(shí)維護(hù)對(duì)觀察對(duì)象、被觀察對(duì)象和上下文的強(qiáng)引用。
?? ? 接收更改通知
?? ? 當(dāng)對(duì)象的被觀察屬性的值發(fā)生變化時(shí),觀察者接收到一個(gè)observeValueForKeyPath:ofObject:change:context: message。所有觀察者都必須實(shí)現(xiàn)這個(gè)方法。
?? ? 觀察對(duì)象提供觸發(fā)通知的鍵路徑、本身作為相關(guān)對(duì)象、包含更改細(xì)節(jié)的字典以及在為該鍵路徑注冊(cè)觀察者時(shí)提供的上下文指針。
?? ? change dictionary條目NSKeyValueChangeKindKey提供關(guān)于發(fā)生的更改類(lèi)型的信息。如果所觀察對(duì)象的值發(fā)生了更改,NSKeyValueChangeKindKey條目將返回NSKeyValueChangeSetting。根據(jù)觀察者注冊(cè)時(shí)指定的選項(xiàng),更改字典中的NSKeyValueChangeOldKey和NSKeyValueChangeNewKey條目包含更改之前和之后的屬性值。如果屬性是對(duì)象,則直接提供值。如果屬性是標(biāo)量或C結(jié)構(gòu),則將值包裝在NSValue對(duì)象中(與鍵值編碼一樣)。
?? ? 如果觀察到的屬性是一對(duì)多關(guān)系,NSKeyValueChangeKindKey條目還指示關(guān)系中的對(duì)象是否分別被插入、刪除或替換為返回nskeyvaluechangeinsert、NSKeyValueChangeRemoval或NSKeyValueChangeReplacement。
?? ? NSKeyValueChangeIndexesKey的更改字典條目是一個(gè)NSIndexSet對(duì)象,它指定了更改關(guān)系中的索引。如果NSKeyValueObservingOptionNew或NSKeyValueObservingOptionOld在觀察者注冊(cè)時(shí)被指定為選項(xiàng),那么變更字典中的NSKeyValueChangeOldKey和NSKeyValueChangeNewKey條目就是數(shù)組,數(shù)組中包含了在變更前后相關(guān)對(duì)象的值。
?? ? 清單3中的示例顯示了Person觀察者的observeValueForKeyPath:ofObject:change:context: implementation,它記錄了balance和interestRate屬性的新舊值,如清單2中所注冊(cè)的那樣。
?? ? 清單3實(shí)現(xiàn)了observeValueForKeyPath:ofObject:change:context:
?? ? - (void)observeValueForKeyPath:(NSString *)keyPath
?? ? ofObject:(id)object
?? ? change:(NSDictionary *)change
?? ? context:(void *)context {
?? ? if (context == PersonAccountBalanceContext) {
?? ? //做一些Balance的事情……
?? ? } else if (context == PersonAccountInterestRateContext) {
?? ? //做一些interest rate…的事情
?? ? } else {
?? ? // 任何未識(shí)別的上下文都必須屬于super
?? ? [super observeValueForKeyPath:keyPath
?? ? ofObject:object
?? ? change:change
?? ? context:context];
?? ? }
?? ? }
?? ? 如果在注冊(cè)觀察者時(shí)指定了空上下文,則將通知的鍵路徑與正在觀察的鍵路徑進(jìn)行比較,以確定發(fā)生了什么更改。如果對(duì)所有觀察到的鍵路徑使用單一上下文,則首先根據(jù)通知的上下文對(duì)其進(jìn)行測(cè)試,并找到匹配項(xiàng),然后使用鍵路徑字符串比較來(lái)確定具體更改了什么。如果您為每個(gè)鍵路徑提供了惟一的上下文(如本文所示),那么一系列簡(jiǎn)單的指針比較將同時(shí)告訴您該通知是否針對(duì)該觀察者,如果是,則告訴您哪些鍵路徑發(fā)生了更改。
?? ? 在任何情況下,當(dāng)觀察者不識(shí)別上下文(或者在簡(jiǎn)單的情況下,任何關(guān)鍵路徑)時(shí),它應(yīng)該總是調(diào)用超類(lèi)的observeValueForKeyPath:ofObject:change:context:的實(shí)現(xiàn),因?yàn)檫@意味著超類(lèi)也注冊(cè)了通知。
?? ? 注意:如果通知傳播到類(lèi)層次結(jié)構(gòu)的頂部,NSObject將拋出nsinternalin一致性異常,因?yàn)檫@是一個(gè)編程錯(cuò)誤:子類(lèi)未能使用它注冊(cè)的通知。
?? ? 作為觀察者刪除一個(gè)對(duì)象
?? ? 通過(guò)向觀察對(duì)象發(fā)送removeObserver:forKeyPath:context: message,指定觀察對(duì)象、鍵路徑和上下文,可以刪除鍵值觀察者。清單4中的示例顯示Person將自己作為balance和interestRate的觀察者刪除。
?? ? 清單4將檢查器作為balance和interestRate的觀察者刪除
?? ? - (void)unregisterAsObserverForAccount:(Account*)account {
?? ? [account removeObserver:self
?? ? forKeyPath:@"balance"
?? ? context:PersonAccountBalanceContext];
?? ? [account removeObserver:self
?? ? forKeyPath:@"interestRate"
?? ? context:PersonAccountInterestRateContext];
?? ? }
?? ? 接收到removeObserver:forKeyPath:context: message后,觀察對(duì)象將不再接收指定鍵路徑和對(duì)象的observeValueForKeyPath:ofObject:change:context: messages。
?? ? 在移除觀察者時(shí),請(qǐng)記住以下幾點(diǎn):
?? ? 1.如果沒(méi)有注冊(cè)為觀察者,請(qǐng)求以觀察者的身份被刪除會(huì)導(dǎo)致NSRangeException異常。您可以調(diào)用removeObserver:forKeyPath:context:對(duì)addObserver:forKeyPath:options:context:的對(duì)應(yīng)調(diào)用一次,或者如果在您的應(yīng)用程序中不可行,則將removeObserver:forKeyPath:context: call放在try/catch塊中,以處理潛在的異常。
?? ? 2.解除分配時(shí),觀察者不會(huì)自動(dòng)刪除自身。被觀察對(duì)象繼續(xù)發(fā)送通知,而不注意觀察者的狀態(tài)。但是,與發(fā)送到已釋放對(duì)象的任何其他消息一樣,更改通知會(huì)觸發(fā)內(nèi)存訪問(wèn)異常。因此,您要確保觀察者在從內(nèi)存中消失之前刪除自己。
?? ? 3.協(xié)議沒(méi)有提供詢(xún)問(wèn)對(duì)象是否是觀察者或被觀察者的方法。構(gòu)造代碼以避免發(fā)布相關(guān)錯(cuò)誤。典型的模式是在觀察者的初始化過(guò)程中注冊(cè)為觀察者(例如在init或viewDidLoad中),在解除分配過(guò)程中取消注冊(cè)(通常在dealloc中),確保正確地對(duì)和有序地添加和刪除消息,并且在觀察者從內(nèi)存中釋放之前取消注冊(cè)。
?? ? 為了被認(rèn)為符合特定屬性的kvo,類(lèi)必須確保以下內(nèi)容:
?? ? 1.類(lèi)必須是符合屬性的鍵值編碼,如在確保KVC遵從性中指定的那樣。
?? ? KVO支持與KVC相同的數(shù)據(jù)類(lèi)型,包括Objective-C對(duì)象以及標(biāo)量和結(jié)構(gòu)支持中列出的標(biāo)量和結(jié)構(gòu)。
?? ? 2.該類(lèi)為屬性發(fā)出KVO更改通知。
?? ? 3.適當(dāng)?shù)刈?cè)依賴(lài)鍵。
?? ? 有兩種技術(shù)可以確保發(fā)出更改通知。NSObject提供了自動(dòng)支持,默認(rèn)情況下,對(duì)符合鍵值編碼的類(lèi)的所有屬性都可用。通常,如果您遵循標(biāo)準(zhǔn)的Cocoa編碼和命名約定,您可以使用自動(dòng)更改通知—您不需要編寫(xiě)任何額外的代碼。
?? ? 手動(dòng)更改通知提供了對(duì)何時(shí)發(fā)出通知的額外控制,并且需要額外的編碼。您可以通過(guò)實(shí)現(xiàn)類(lèi)方法automaticallyNotifiesObserversForKey:來(lái)控制子類(lèi)屬性的自動(dòng)通知。
?? ? 自動(dòng)更改通知
?? ? NSObject提供了自動(dòng)鍵值更改通知的基本實(shí)現(xiàn)。自動(dòng)鍵值更改通知通知觀察者使用符合鍵值的訪問(wèn)器以及鍵值編碼方法所做的更改。由mutableArrayValueForKey:返回的集合代理對(duì)象也支持自動(dòng)通知。
?? ? 清單1所示的示例會(huì)將更改通知屬性名的任何觀察者。
?? ? 清單1發(fā)出KVO更改通知的方法調(diào)用示例
?? ? // 調(diào)用訪問(wèn)器方法。
?? ? [account setName:@"Savings"];
?? ? // 使用setValue: forKey:。
?? ? [account setValue:@"Savings" forKey:@"name"];
?? ? // 使用密鑰路徑,其中‘a(chǎn)ccount’是‘document’的kvc兼容屬性。
?? ? [document setValue:@"Savings" forKeyPath:@"account.name"];
?? ? //使用mutableArrayValueForKey:檢索關(guān)系代理對(duì)象。
?? ? Transaction *newTransaction = <#Create a new transaction for the account#><#為帳戶(hù)#>創(chuàng)建一個(gè)新事務(wù);
?? ? NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
?? ? [transactions addObject:newTransaction];
?? ? 手動(dòng)更改通知
?? ? 在某些情況下,您可能希望控制通知流程,例如,最小化由于應(yīng)用程序特定原因而不必要的觸發(fā)通知,或者將一些更改分組到單個(gè)通知中。手動(dòng)更改通知提供了這樣做的方法。
?? ? 手動(dòng)通知和自動(dòng)通知并不相互排斥。除了已經(jīng)存在的自動(dòng)通知之外,您還可以自由地發(fā)出手動(dòng)通知。更典型的是,您可能希望完全控制特定屬性的通知。在本例中,您覆蓋了automaticallyNotifiesObserversForKey:的NSObject實(shí)現(xiàn)。對(duì)于您希望阻止其自動(dòng)通知的屬性,automaticallyNotifiesObserversForKey:的子類(lèi)實(shí)現(xiàn)應(yīng)該返回NO。子類(lèi)實(shí)現(xiàn)應(yīng)該為任何未識(shí)別的鍵調(diào)用super。清單2中的示例支持balance屬性的手動(dòng)通知,允許超類(lèi)確定所有其他鍵的通知。
?? ? 例二automaticallyNotifiesObserversForKey:
?? ? + (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)觀察者通知,您可以在更改值之前調(diào)用willChangeValueForKey:,在更改值之后調(diào)用didChangeValueForKey:。清單3中的示例實(shí)現(xiàn)了balance屬性的手動(dòng)通知。
?? ? 清單3實(shí)現(xiàn)手動(dòng)通知的示例訪問(wèn)器方法
?? ? - (void)setBalance:(double)theBalance {
?? ? [self willChangeValueForKey:@"balance"];
?? ? _balance = theBalance;
?? ? [self didChangeValueForKey:@"balance"];
?? ? }
?? ? 您可以通過(guò)首先檢查值是否已更改來(lái)最小化發(fā)送不必要的通知。清單4中的示例測(cè)試balance的值,并且只在余額發(fā)生更改時(shí)提供通知。
?? ? 清單4在提供通知之前測(cè)試更改的值
?? ? - (void)setBalance:(double)theBalance {
?? ? if (theBalance != _balance) {
?? ? [self willChangeValueForKey:@"balance"];
?? ? _balance = theBalance;
?? ? [self didChangeValueForKey:@"balance"];
?? ? }
?? ? }
?? ? 如果單個(gè)操作導(dǎo)致多個(gè)鍵發(fā)生更改,則必須嵌套更改通知,如清單5所示。
?? ? 清單5嵌套多個(gè)鍵的更改通知
?? ? - (void)setBalance:(double)theBalance {
?? ? [self willChangeValueForKey:@"balance"];
?? ? [self willChangeValueForKey:@"itemChanged"];
?? ? _balance = theBalance;
?? ? _itemChanged = _itemChanged+1;
?? ? [self didChangeValueForKey:@"itemChanged"];
?? ? [self didChangeValueForKey:@"balance"];
?? ? }
?? ? 對(duì)于有序到多關(guān)系,不僅必須指定更改的鍵,還必須指定更改的類(lèi)型和涉及的對(duì)象的索引。更改的類(lèi)型是NSKeyValueChange,它指定nskeyvaluechangeinsert、NSKeyValueChangeRemoval或NSKeyValueChangeReplacement。受影響對(duì)象的索引作為NSIndexSet對(duì)象傳遞。
?? ? 清單6中的代碼片段演示了如何包裝to-many關(guān)系事務(wù)中刪除的對(duì)象。
?? ? 清單6在一對(duì)多關(guān)系中實(shí)現(xiàn)手動(dòng)觀察者通知
?? ? - (void)removeTransactionsAtIndexes:(NSIndexSet *)indexes {
?? ? [self willChange:NSKeyValueChangeRemoval
?? ? valuesAtIndexes:indexes forKey:@"transactions"];
?? ? //刪除指定索引處的事務(wù)對(duì)象。
?? ? [self didChange:NSKeyValueChangeRemoval
?? ? valuesAtIndexes:indexes forKey:@"transactions"];
?? ? }
?? ? 注冊(cè)相關(guān)的鍵
?? ? 在許多情況下,一個(gè)屬性的值取決于另一個(gè)對(duì)象中的一個(gè)或多個(gè)其他屬性的值。如果一個(gè)屬性的值發(fā)生了變化,那么派生屬性的值也應(yīng)該標(biāo)記以供更改。如何確保為這些依賴(lài)屬性發(fā)布觀察通知的鍵值取決于關(guān)系的基數(shù)。
?? ? 一個(gè)關(guān)系
?? ? 要自動(dòng)觸發(fā)一對(duì)一關(guān)系的通知,您應(yīng)該覆蓋keyPathsForValuesAffectingValueForKey:或者實(shí)現(xiàn)一個(gè)合適的方法,該方法遵循它為注冊(cè)依賴(lài)鍵定義的模式。
?? ? 例如,一個(gè)人的全名取決于他的姓和名。返回全名的方法可以這樣寫(xiě):
?? ? - (NSString *)fullName {
?? ? return [NSString stringWithFormat:@"%@ %@",firstName, lastName];
?? ? }
?? ? 觀察fullName屬性的應(yīng)用程序必須在firstName或lastName屬性更改時(shí)得到通知,因?yàn)樗鼈儠?huì)影響屬性的值。
?? ? 一種解決方案是覆蓋keyPathsForValuesAffectingValueForKey:指定person的fullName屬性依賴(lài)于lastName和firstName屬性。清單1顯示了這種依賴(lài)關(guān)系的示例實(shí)現(xiàn):
?? ? 清單1 keyPathsForValuesAffectingValueForKey的示例實(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;
?? ? }
?? ? 您的覆蓋通常應(yīng)該調(diào)用super并返回一個(gè)集合,該集合中包含由此而產(chǎn)生的任何成員(以便不干擾超類(lèi)中該方法的覆蓋)。
?? ? 您還可以通過(guò)實(shí)現(xiàn)一個(gè)類(lèi)方法來(lái)實(shí)現(xiàn)相同的結(jié)果,該類(lèi)方法遵循影響的keypathsforvaluesnaming約定,其中是依賴(lài)于值的屬性的名稱(chēng)(第一個(gè)大寫(xiě)字母)。使用此模式,可以將清單1中的代碼重寫(xiě)為名為keyPathsForValuesAffectingFullName的類(lèi)方法,如清單2所示。
?? ? 清單2影響命名約定的keypathsforvaluesaffect的示例實(shí)現(xiàn)
?? ? + (NSSet *)keyPathsForValuesAffectingFullName {
?? ? return [NSSet setWithObjects:@"lastName", @"firstName", nil];
?? ? }
?? ? 當(dāng)您使用類(lèi)別向現(xiàn)有類(lèi)添加計(jì)算屬性時(shí),您不能覆蓋keyPathsForValuesAffectingValueForKey:方法,因?yàn)槟粦?yīng)該覆蓋類(lèi)別中的方法。在這種情況下,實(shí)現(xiàn)一個(gè)匹配的keypathsforvaluesaffect 類(lèi)方法,以利用這種機(jī)制。
?? ? 注意:您不能通過(guò)實(shí)現(xiàn)keyPathsForValuesAffectingValueForKey:來(lái)設(shè)置對(duì)多個(gè)關(guān)系的依賴(lài)關(guān)系。相反,您必須觀察to-many集合中每個(gè)對(duì)象的適當(dāng)屬性,并通過(guò)自己更新依賴(lài)鍵來(lái)響應(yīng)其值的更改。
?? ? 很多關(guān)系
?? ? 方法不支持包含對(duì)多關(guān)系的鍵路徑。例如,假設(shè)您有一個(gè)部門(mén)對(duì)象,該對(duì)象與一個(gè)雇員(雇員)具有多對(duì)多的關(guān)系,并且雇員具有一個(gè)salary屬性。您可能希望Department對(duì)象具有totalSalary屬性,該屬性依賴(lài)于關(guān)系中所有員工的工資。例如,keypathsforvaluesaffect total salary和return employees就不能這樣做。薪水是關(guān)鍵。
?? ? 兩種情況都有兩種可能的解決方案:
?? ? 您可以使用鍵值觀察來(lái)將父節(jié)點(diǎn)(在本例中為Department)注冊(cè)為所有子節(jié)點(diǎn)(在本例中為employee)的相關(guān)屬性的觀察者。您必須以觀察者的身份添加和刪除父對(duì)象,因?yàn)樵陉P(guān)系中添加和刪除子對(duì)象。
?? ? - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
?? ? if (context == totalSalaryContext) {
?? ? [self updateTotalSalary];
?? ? }
?? ? else
?? ? //處理其他觀察和/或調(diào)用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,可以將父對(duì)象注冊(cè)到應(yīng)用程序的通知中心,作為其托管對(duì)象上下文的觀察者。父節(jié)點(diǎn)應(yīng)該以類(lèi)似于觀察鍵值的方式響應(yīng)子節(jié)點(diǎn)發(fā)布的相關(guān)更改通知。
?? ? 觀察實(shí)現(xiàn)細(xì)節(jié)的鍵值
?? ? 自動(dòng)鍵值觀察是使用isa- swizzle技術(shù)實(shí)現(xiàn)的。
?? ? 顧名思義,isa指針指向維護(hù)分派表的對(duì)象的類(lèi)。這個(gè)分派表本質(zhì)上包含指向類(lèi)實(shí)現(xiàn)的方法和其他數(shù)據(jù)的指針。
?? ? 當(dāng)一個(gè)觀察者注冊(cè)了一個(gè)對(duì)象的屬性時(shí),被觀察對(duì)象的isa指針被修改,指向一個(gè)中間類(lèi)而不是真正的類(lèi)。因此,isa指針的值不一定反映實(shí)例的實(shí)際類(lèi)。
?? ? 永遠(yuǎn)不要依賴(lài)isa指針來(lái)確定類(lèi)的成員。相反,您應(yīng)該使用類(lèi)方法來(lái)確定對(duì)象實(shí)例的類(lèi)