來源
key-value observing 是一種機(jī)制,當(dāng)一些對(duì)象的指定屬性被改變時(shí),通知另一些對(duì)象.
這個(gè)機(jī)制對(duì)于App里的model和controller之間的聯(lián)系是非常有用的.一個(gè)controller對(duì)象通常都會(huì)觀察models對(duì)象的屬性,并且view對(duì)象也會(huì)通過controller對(duì)象觀察model對(duì)象的屬性.另外,一個(gè)model對(duì)象也會(huì)觀察另一個(gè)依賴的model對(duì)象的屬性.
你可以觀察普通的屬性,to-one關(guān)系,to-many關(guān)系(數(shù)組等等).to-many的觀察會(huì)被不同的改變類型而被通知,比如在當(dāng)前改變類型中,哪些對(duì)象會(huì)被需要(插入,刪除,替換等)
注冊(cè) KVO
你必須執(zhí)行以下步驟來確保對(duì)象能接收KVO通知:
- 使用
addObserver:forKeyPath:options:context:.
方法,把觀察者注冊(cè)到被觀察者里. - 在觀察者對(duì)象里實(shí)現(xiàn)
observeValueForKeyPath:ofObject:change:context:
方法,去接收改變通知. - 當(dāng)不再需要接收信息時(shí),用
removeObserver:forKeyPath:
方法去除注冊(cè).至少觀察者在被內(nèi)存釋放前你要調(diào)用這個(gè)方法.
KVO的
addObserver:forKeyPath:options:context:
方法不會(huì)保持強(qiáng)引用到觀察對(duì)象,被觀察對(duì)象或context.你應(yīng)該保證你自己保持這些的強(qiáng)引用
如果一個(gè)notification被傳到最頂層,
NSObject
就會(huì)拋出一個(gè)NSInternalInconsistencyException
,那是因?yàn)檫@是一個(gè)編程錯(cuò)誤:一個(gè)注冊(cè)在子類的通知被漏了,傳到了NSObject
.在observeValueForKeyPath:ofObject:change:context:
都會(huì)實(shí)現(xiàn)super
移除一個(gè)觀察者,要注意以下幾點(diǎn):
- 移除一個(gè)未注冊(cè)的觀察會(huì)引用
NSRangeException
異常.要不在removeObserver:forKeyPath:context:
調(diào)用前,確保調(diào)用了addObserver:forKeyPath:options:context:
.或者你把removeObserver:forKeyPath:context:
放在 try/catch里去處理異常. - 你一個(gè)觀察對(duì)象不會(huì)在對(duì)象釋放時(shí),自動(dòng)移除自己.被觀察對(duì)象還會(huì)繼續(xù)發(fā)送通知,不會(huì)知道觀察者的狀態(tài).這樣,一個(gè)通知就有可能會(huì)發(fā)送一個(gè)已釋放的對(duì)象,引用一個(gè)內(nèi)存訪問異常.所以,你要確保在觀察者被釋放之前它們要移除自己.
- 通常的模式是在觀察者初始化時(shí)注冊(cè)通知(比如:init 或viewDidLoad),并且在釋放時(shí)移除通知(比如:
dealloc
或deinit
).保證它們成對(duì)的順序的增加移除通知.
自動(dòng)改變通知
NSObject
提供了自動(dòng)改變通知的基礎(chǔ)實(shí)現(xiàn).以下代碼都會(huì)實(shí)現(xiàn)自動(dòng)通知:
// 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 = <#Create a new transaction for the account#>;
NSMutableArray *transactions = [account mutableArrayValueForKey:@"transactions"];
[transactions addObject:newTransaction];
手動(dòng)操控改變通知
在一些情況,你可能想要控制一個(gè)處理的通知,比如最少的不必要的通知,或把一組通知集合在一個(gè)通知里.
你要重寫NSObject
的類方法automaticallyNotifiesObserversForKey:
.
以下代碼把"balance"的通知給排除了.
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
class override func automaticallyNotifiesObservers(forKey key:String) -> Bool{
<#Code#>
}
為了實(shí)現(xiàn)手動(dòng)通知,你要在改變對(duì)象之前調(diào)用willChangeValueForKey:
,并且在改變對(duì)象之后調(diào)用didChangeValueForKey:
.
- (void)setBalance:(double)theBalance {
if (theBalance != _balance) {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
}
如果一個(gè)操作引起來了多個(gè)keys的變化,你要去嵌套改變通知:
- (void)setBalance:(double)theBalance {
[self willChangeValueForKey:@"balance"];
[self willChangeValueForKey:@"itemChanged"];
_balance = theBalance;
_itemChanged = _itemChanged+1;
[self didChangeValueForKey:@"itemChanged"];
[self didChangeValueForKey:@"balance"];
}
如果是在一個(gè)有序的to-many的關(guān)系(比如:Array),你不僅要指出key,還要指出改變的類型和被改地方的索引.
- (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"];
}