- 被觀察的物件必須相容KVO。一般情況下,如果物件是繼承自
NSObject
的話,那麼該物件及其屬性都會自動相容KVO。 - 觀察者呼叫
addObserver:forKeyPath:options:context:
方法來將自身新增為觀察者,觀察物件的key path - 為了獲取被觀察物件的key path的變化,觀察者還需要實現
observeValueForKeyPath:ofObject:change:context:
。 - 在結束觀察(比方說物件被釋放等情況)的話,觀察者需要呼叫
removeObserver:forKeyPath:
來移除觀察。 - KVO跟
NSNotificationCenter
不同的一點是,KVO沒有一個集中的物件來提供通知給所有觀察者。在被觀察的物件發生變化時,KVO會直接給觀察者傳送訊息,沒有其他多餘的操作。
addObserver:forKeyPath:options:context:
-
options:
NSKeyValueObservingOptionOld
: 獲取改變之前的值NSKeyValueObservingOptionNew
: 獲取改變之後的值NSKeyValueObservingOptionInitial
: 當某個屬性進行了初始化時傳送通知。只會接收到一次NSKeyValueObservingOptionPrior
: 在發生變回之前傳送通知
-
context:
可以賦值為NULL,但是如果觀察者的父類也同樣觀察者同一個key path時,會出現問題。
為了避免問題的發生,最好的方式是在類中建立一個靜態變數來作為context.
static void *PersonAccountBalanceContext = &PersonAccountBalanceContext; static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext; 複製程式碼
observeValueForKeyPath:ofObject:change:context:
所有觀察者都必須實現此方法來接收通知!
removeObserver:forKeyPath:context:
- 如果你的物件沒有被註冊為觀察者,而你又呼叫了移除觀察者的方法的話,會出現
NSRangeException
錯誤。避免出現此錯誤的話,最好是註冊和移除的方法配套使用,或者是將移除的方法放在try/catch中執行。 - 觀察者物件在被摧毀時並不會自動移除自身的觀察者身份。
- 通常在
init
或者viewDidLoad
中註冊觀察者身份,在dealloc
中移除。
自動傳送訊息
e.g. 能夠引起KVO訊息傳送的例子
// 呼叫訪問器方法
[account setName:@"Savings"];
// 使用 setValue:forKey:
[account setValue:@"Savings" forKey:@"name"];
// 使用 setValue:forKeyPath:
[document setValue:@"Savings" forKeyPath:@"account.name"];
複製程式碼
手動傳送訊息
某些情況下,你可能會想自己來管理訊息的程式。比方說因為某些原因減少觸發的訊息數,又或者將幾個通知合併為一個。要完成上面的需求的話,你就需要手動觸發KVO的訊息傳送。
手動和自動傳送訊息並不會衝突。一般情況下,我們只會對某一個特殊的物件進行手動的訊息處理。這樣的話,我們在繼承NSObject
的時候,需要複寫automaticallyNotifiesObserversForKey:
方法,並且返回NO
.
e.g.
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:@"balance"]) {
automatic = NO;
}
else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
複製程式碼
然後,在該屬性的訪問器方法中,呼叫willChangeValueForKey
和didChangeValueForKey
- (void)setBalance:(double)theBalance {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
// or
- (void)setBalance:(double)theBalance {
if (theBalance != _balance) {
[self willChangeValueForKey:@"balance"];
_balance = theBalance;
[self didChangeValueForKey:@"balance"];
}
}
// 如果是一對多的關係的話,還需要修改物件改變的型別(NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, 或者 NSKeyValueChangeReplacement)以及所涉及到index
- (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"];
}
複製程式碼
- To-One RelationShips
要自動觸發一對一關係的通知的話,需要複寫keyPathsForValuesAffectingValueForKey:
比方說有一個fullName
的屬性
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@ %@", firstName, lastName];
}
複製程式碼
fullName的屬性由firstName和lastName決定。在複寫的時候,需要指定這兩個相關的屬性
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
NSSet *keyPaths = [super keyPathsForValuesAffectingValueForKey:key];
if ([key isEqualToString:@"fullName"]) {
NSArray *affectingKeys = @[@"lastName", @"firstName"];
keyPaths = [keyPaths setByAppendingObjectsFromArray:affectingKeys];
}
return keyPaths;
}
// 可以更改為
+ (NSSet *)keyPathsForValuesAffectingFullName {
return [NSSet setWithObjects:@"lastName", @"firstName", nil];
}
複製程式碼
- To-Many RelationShips
- 在被觀察者的相關屬性中註冊觀察者。
- (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的話,可以將managed context object註冊為觀察者。