使用稱爲isa swizzling的技術實現自動鍵值觀察。顧名思義,isa指針指向維護分派表的對象類。這個分派表本質上包含指向類實現的方法的指針以及其他數據。
當一個觀察者爲一個對象的屬性註冊時,被觀察對象的isa指針被修改,指向一箇中間類而不是真正的類。因此,isa指針的值不一定反映實例的實際類。
決不能依賴isa指針來確定類成員身份。相反,您應該使用class方法來確定對象實例的類。
1.準備工作
先定義一個類NXBookclass
,再定義好一個工具方法打印前後類的變化,
@interface NXBookclass : NSObject {
@public NSInteger _priceValue;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSMutableArray *descriptions;
@end
@implementation NXBookclass
//+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
// return YES;
//}
@end
@interface NXApi : NSObject
+ (NSMutableDictionary *)descriptionClass:(Class)cls;
@end
@implementation NXApi
+ (NSMutableDictionary *)descriptionClass:(Class)cls {
NSMutableDictionary *dicValue = [NSMutableDictionary dictionaryWithCapacity:5];
while (cls && cls != [NSObject class]) {
NSMutableDictionary *dicSubvalue = [NSMutableDictionary dictionaryWithCapacity:5];
dicSubvalue[@"varList"] = [NXApi varList:cls forward:false];
dicSubvalue[@"methodList"] = [NXApi methodList:cls forward:false];
dicSubvalue[@"metaClass"] = [NXApi metaClass:cls forward:false];
[dicValue setObject:dicSubvalue forKey:[NSString stringWithCString:class_getName(cls) encoding:NSUTF8StringEncoding]];
cls = class_getSuperclass(cls);
}
return dicValue;
}
@end
2.開始觀察
@interface NXKVO : NSObject
- (void)KVO;
@end
@implementation NXKVO
- (void)KVO{
NXBookclass *bookclass = [[NXBookclass alloc] init];
NSLog(@"觀察前:%@", [NXApi descriptionClass:object_getClass(bookclass)]);//打印觀察之前的類信息
{
[bookclass addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:NULL];
NSLog(@"觀察中:%@", [NXApi descriptionClass:object_getClass(bookclass)]);//打印觀察之後的類信息
bookclass.name = @"Swift";
[bookclass removeObserver:self forKeyPath:@"name"];
}
NSLog(@"取消觀察後:%@", [NXApi descriptionClass:object_getClass(bookclass)]);//打印取消觀察之後的類信息
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%s, key=%@, change=%@", __func__, keyPath, change);
}
@end
運行如上代碼,打印信息如下:
觀察前或取消觀察後:
{
NXBookclass = {
metaClass = (
NXBookclass
);
methodList = (
name,
".cxx_destruct",
"setName:",
descriptions,
"setDescriptions:"
);
propertyList = (
name,
descriptions
);
varList = (
"_priceValue",
"_name",
"_descriptions"
);
};
}
觀察中:
{
"NSKVONotifying_NXBookclass" = {
metaClass = (
"NSKVONotifying_NXBookclass"
);
methodList = (
"setName:",
class,
dealloc,
"_isKVOA"
);
propertyList = (
);
varList = (
);
};
NXBookclass = {
metaClass = (
NXBookclass
);
methodList = (
name,
".cxx_destruct",
"setName:",
descriptions,
"setDescriptions:"
);
propertyList = (
name,
descriptions
);
varList = (
"_priceValue",
"_name",
"_descriptions"
);
};
}
觀察的回調信息:
key=name, change={
kind = 1;
new = Swift;
old = "<null>";
}
通過打印的日誌信息,我們可以看到,我們的bookclass的的name屬性的觀察是成功的,name的值由null變成了Swift。
通過打印的日誌我們可以看到如下現象:觀察中的類與觀察前後的類信息不一樣,默認的類是NXBookclass
->NSObject
;而觀察中的類是NSKVONotifying_NXBookclass
->NXBookclass
->NSObject
。並且在NSKVONotifying_NXBookclass
這個類中出現了setName:
,class
,dealloc
,_isKVOA
4個新增的方法。
1.iOS對KVO底層的實現,其實是通過派生一個NSKVONotifying_XXXX
的類。bookclass.is指向NSKVONotifying_NXBookclass
, 我們通過訪問[bookclass class]
會發現返回的仍然是NXBookclass
, 這是因爲NSKVONotifying_NXBookclass
重寫了class類,返回了NXBookclass
。
2.觀察實際上是通過重寫觀察的屬性的setter方法來實現的。重寫setName:
方法:
- (void)setName:(NSString *)name {
if(_name != name){
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
}
3._isKVOA
,用來標記是否是一個KVO的實例,通過調試發現_isKVOA == YES。在最後一個removeObserver:forKeyPath:
之後變成NO。
4.dealloc
方法中,會將isa指回去。
5.通過觀察前,觀察後,取消觀察後對NSClassFromString(@"NSKVONotifying_NXBookclass")
訪問,可以發現首次觀察之前結果爲null,觀察中和取消觀察後都存在,也就是取消觀察後NSKVONotifying_NXBookclass不會消失。如果存在自定義的類NSKVONotifying_NXBookclass,那麼在進行註冊觀察的時候會失敗,也就是觀察並不會生效。
6.默認情況下類的automaticallyNotifiesObserversForKey :
返回YES,runtime會自動派生子類幫助我們做好監聽。我們可以重寫該方法 返回NO,並且自己在當前類中重寫setName:
方法,這樣系統不會派生新的類。