在 iOS 中,KVO(Key-Value Observing)是一個強大的觀察機制,它的底層實現相對復雜。KVO 利用 Objective-C 的動態特性,為對象的屬性提供觀察能力。
KVO 的底層實現
1. 動態子類化
當一個對象的屬性被添加觀察者時,KVO 會在運行時動態地創建該對象的子類,并重寫該屬性的 setter 方法。
- 動態創建子類:KVO 會創建一個新的類,這個新類是被觀察對象的子類,通常這個類的名字是
_NSKVOClassName_ClassName
形式。 - 重寫 setter 方法:在這個動態創建的子類中,KVO 會重寫被觀察屬性的 setter 方法。
2. 重寫 setter 方法
重寫后的 setter 方法在屬性值發生變化時,會進行以下操作:
- 觸發
willChangeValue(forKey:)
:通知即將發生變化。 - 調用原始 setter 方法:通過消息轉發機制調用原始的 setter 方法,以實際更新屬性值。
- 觸發
didChangeValue(forKey:)
:通知變化已經發生,觸發觀察者回調。
3. 動態方法解析
在 KVO 動態創建的子類中,使用 method_setImplementation
方法來重寫屬性的 setter 方法。
void setAge(id self, SEL _cmd, int newAge) {[self willChangeValueForKey:@"age"];struct objc_super superStruct = {.receiver = self,.super_class = class_getSuperclass(object_getClass(self))};((void (*)(struct objc_super *, SEL, int))objc_msgSendSuper)(&superStruct, _cmd, newAge);[self didChangeValueForKey:@"age"];
}
KVO 的實現細節
以下是一個簡單的示例,展示了 KVO 的一些底層實現細節:
@interface Person : NSObject
@property (nonatomic, assign) int age;
@end@implementation Person
@endPerson *person = [[Person alloc] init];
NSLog(@"Original class: %@", object_getClass(person)); // 輸出原始類[person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"Class after adding observer: %@", object_getClass(person)); // 輸出動態子類[person setAge:30];
[person removeObserver:self forKeyPath:@"age"];
KVO 的工作流程
-
添加觀察者:
- 調用
addObserver:forKeyPath:options:context:
方法時,KVO 會動態創建子類并重寫 setter 方法。 - 原始對象的類指針(
isa
指針)被修改為新創建的子類。
- 調用
-
觸發觀察:
- 當屬性值發生變化時,調用重寫后的 setter 方法。
- 先觸發
willChangeValueForKey:
,然后調用原始 setter 方法更新屬性值,最后觸發didChangeValueForKey:
。 - 觸發
didChangeValueForKey:
時,會通知所有觀察者屬性值已經改變。
-
移除觀察者:
- 調用
removeObserver:forKeyPath:
方法時,KVO 會將類指針恢復為原始類,并移除重寫的 setter 方法。
- 調用
注意事項
- 自動 KVO:KVO 默認僅支持通過 setter 方法修改屬性值的情況。直接修改實例變量不會觸發 KVO。
- 手動觸發 KVO:如果需要手動觸發 KVO,可以調用
willChangeValue(forKey:)
和didChangeValue(forKey:)
方法。
[self willChangeValueForKey:@"age"];
_age = newValue;
[self didChangeValueForKey:@"age"];
總結
KVO 是 iOS 中基于動態特性實現的觀察機制,通過動態子類化和方法重寫實現。當屬性值變化時,KVO 會通知所有注冊的觀察者。這一機制使得對象間的通信更加靈活和高效,但也需要注意在使用過程中正確添加和移除觀察者,以避免內存泄漏或崩潰。