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