setX:/x)。observeValueForKeyPath:ofObject:change:context: 接收回呼。addObserver:forKeyPath:options:context: 註冊。必須在 dealloc 中呼叫 removeObserver:forKeyPath:,否則物件釋放後收到通知會崩潰。
options 詳解| 常數 | 含義 |
|---|---|
| NSKeyValueObservingOptionNew | change 字典包含 NSKeyValueChangeNewKey(新值) |
| NSKeyValueObservingOptionOld | 包含 NSKeyValueChangeOldKey(舊值) |
| NSKeyValueObservingOptionInitial | 註冊瞬間立即發送一次通知(舊值為 nil) |
| NSKeyValueObservingOptionPrior | 變更前/後各發一次(NSKeyValueChangeNotificationIsPriorKey) |
NSKeyValueChangeKindKey:變更類型(Setting/Insertion/Removal/Replacement)NSKeyValueChangeNewKey、OldKey、IndexesKey、IsPriorKey
@interface Son : NSObject
@property (nonatomic, copy) NSString *girl_friend_name;
- (void)fall_in_love_with:(NSString*)name;
- (void)think;
@end
@implementation Son
- (void)fall_in_love_with:(NSString*)name { self.girl_friend_name = name; }
- (void)think { NSLog(@"My love girl is %@ ... Does baba know?", girl_friend_name); }
@end
@interface Baba : NSObject
@property (strong) Son *son;
@end
@implementation Baba
- (instancetype)initWithSon:(Son*)s {
if (self = [super init]) {
_son = s;
[s addObserver:self
forKeyPath:@"girl_friend_name"
options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial
context:nil];
}
return self;
}
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)obj change:(NSDictionary*)change context:(void*)ctx {
NSString *old = change[NSKeyValueChangeOldKey];
NSString *new = change[NSKeyValueChangeNewKey];
if (!old) { NSLog(@"First time induction!"); return; }
if ([new isEqualToString:@"libinbin"]) [obj fall_in_love_with:@"fanbinbin"]; // 強行改變
NSLog(@"Son's %@ changed from %@ to %@", keyPath, old, new);
}
- (void)dealloc { [self.son removeObserver:self forKeyPath:@"girl_friend_name"]; }
@end
重寫類別方法 +automaticallyNotifiesObserversForKey: 返回 NO 關閉自動通知,再在 setter 中手動呼叫:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key {
return ![key isEqualToString:@"girl_friend_name"];
}
- (void)setGirl_friend_name:(NSString*)name {
[self willChangeValueForKey:@"girl_friend_name"];
_girl_friend_name = @"女學霸"; // 假裝給老爸看
[self didChangeValueForKey:@"girl_friend_name"];
_girl_friend_name = name; // 真實值
}
結果:老爸只看到「女學霸」,兒子實際女友不變,達到隱私保護與誤導並存。
dealloc 不能呼叫 [super dealloc]。removeObserver,再釋放被觀察物件。context 參數可用於區分多個觀察同一鍵路徑的觀察者,傳遞 self 或唯一指標。KVO 是 Cocoa 綁定機制的基石,掌握自動/手動通知切換、change 字典解析、生命週期管理 三大核心,即可靈活實現屬性監聽、資料同步、UI 自動更新等模式。