說說背景,研究下面的程式碼時,KVO後[obj class]與object_getClass(id obj)的結果竟會不一致?
PersonModel *aPersonModel = [[PersonModel alloc] init]; aPersonModel.name=@"lisi"; NSLog(@"之前%@ %@",[aPersonModel class],object_getClass(aPersonModel)); [aPersonModel addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; NSLog(@"之後%@ %@",[aPersonModel class],object_getClass(aPersonModel)); aPersonModel.name=@"zhangsan"; //[aPersonModel removeObserver:self forKeyPath:@"name"];
檢視 NSObject 底層程式碼
+ (Class)class { return self; } - (Class)class { return object_getClass(self); }
Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; }
很明顯,例項方法 class 內部呼叫和 object_getClass 毫無區別,都是獲得物件的 isa 指標。類方法直接返回的本身。那麼為啥 KVO 後[obj class]與object_getClass(id obj)的結果竟會不一致?
列印一下 KVO 後,NSKVONotifying_PersonModel 裡的方法。發現系統內部,重寫了 class 方法,直接返回的 KVO 之前的類物件。
Class cls = object_getClass(aPersonModel);
[self printMethodList:cls];
- (void)printMethodList:(Class)cls { unsigned int outCount; Method* methods = class_copyMethodList(cls,&outCount); for (int i = 0; i < outCount ; i++) { SEL name = method_getName(methods[i]); NSString *strName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding]; NSLog(@"selName : %@",strName); } }
結果列印:
2021-06-04 14:40:02.434062+0800 MsgSendTest[70021:7491894] selName : setName: 2021-06-04 14:40:02.434233+0800 MsgSendTest[70021:7491894] selName : class 2021-06-04 14:40:02.434368+0800 MsgSendTest[70021:7491894] selName : dealloc 2021-06-04 14:40:02.434487+0800 MsgSendTest[70021:7491894] selName : _isKVOA
重新迴歸KVO的原理:
1.比如原先例項 aPersonModel 的isa指標指向的是 PersonModel,那麼當你在第一次呼叫過
[aPersonModel addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
方法後,runtime 會建立一個新的類,類名以NSKVONotifing開頭叫 NSKVONotifying_PersonModel,同時更改例項 aPersonModel 的 isa 的指標,將其指向 NSKVONotifying_PersonModel 。
2.在 NSKVONotifying_PersonModel 中重寫觀察的屬性 name 的 setter 方法 setName ,並在它裡面呼叫
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
方法。這樣當改變屬性 name 的值時,外面就會得到通知。
3.在 NSKVONotifying_PersonModel 中重寫 - (Class) class 方法,返回原先的 isa 指向的類(在這個例子中就是 PersonModel )。這就是為什麼[aPersonModel class]與object_getClass(aPersonModel) 返回的結果不一致的原因。