@blueGhost
2017-06-30T09:16:51.000000Z
字数 6329
阅读 1661
iOS
参考文献:
Apple官方文档 Key-Value Observing Programming Guide
KVO internal implementation
NShisper Key-Value Observing tutorial
- KVO内部通过isa-swizzling实现
- 当对某个对象注册KVO,则被观察对象的isa指针会指向一个新的class(我们姑且叫做“中间人class”intermediate class)
- 这个新的class已经不是原来对象的class,充其量只是一个中间代理,因此apple不推荐直接通过isa指针取出对象的class,而是推荐用class方法
- 代理class重写了property的setter 等相关方法的IMP实现,以便即使通知观察者property的变动(变量初始化的通知是怎么弄的还得查纠)
- 代理class和原来真正的class之间有某种关联并且对外隐藏,使得原来class维持的信息依旧有效(比如继承链)
- 为什么不直接swizzle property的setter等方法,而是swizzle isa呢,因为直接交换set/get,会导致所有该class的instances都加入了这些新逻辑
#import <Foundation/Foundation.h>#import "FFPerson.h"#import <objc/runtime.h>#import <objc/message.h>void printChar(char *ch);NSArray * ClassMethodNames(Class c);void printDes(NSString *name, id obj);int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...FFPerson *p1 = [[FFPerson alloc]init];FFPerson *p2 = [[FFPerson alloc]init];FFPerson *p3 = [[FFPerson alloc]init];[p2 addObserver:p2 forKeyPath:@"name" options:0 context:NULL];[p3 addObserver:p3 forKeyPath:@"info" options:0 context:NULL];printDes(@"p1", p1);printDes(@"p2", p2);printDes(@"p3", p3);// obj->isa,isa指针指向obj对象的class,class包含了obj对象关于类的信息和实现// object_getClass(obj)相当于旧版runtime的obj->isa,目前版本的isa已经对外不可见了printf("Using libobjc setName, p1: %p, p2: %p, p3: %p\n",method_getImplementation(class_getInstanceMethod(object_getClass(p1),@selector(setName:))),method_getImplementation(class_getInstanceMethod(object_getClass(p2),@selector(setName:))),method_getImplementation(class_getInstanceMethod(object_getClass(p3),@selector(setName:))));printf("Using libobjc name, p1: %p, p2: %p, p3: %p\n",method_getImplementation(class_getInstanceMethod(object_getClass(p1),@selector(name))),method_getImplementation(class_getInstanceMethod(object_getClass(p2),@selector(name))),method_getImplementation(class_getInstanceMethod(object_getClass(p3),@selector(name))));printf("Using libobjc setInfo, p1: %p, p2: %p, p3: %p\n",method_getImplementation(class_getInstanceMethod(object_getClass(p1),@selector(setInfo:))),method_getImplementation(class_getInstanceMethod(object_getClass(p2),@selector(setInfo:))),method_getImplementation(class_getInstanceMethod(object_getClass(p3),@selector(setInfo:))));printf("Using libobjc info, p1: %p, p2: %p, p3: %p\n",method_getImplementation(class_getInstanceMethod(object_getClass(p1),@selector(info))),method_getImplementation(class_getInstanceMethod(object_getClass(p2),@selector(info))),method_getImplementation(class_getInstanceMethod(object_getClass(p3),@selector(info))));}return 0;}void printDes(NSString *name, id obj){NSString *des = [NSString stringWithFormat: @"%@: %@\n\tNSObject class %s\n\tlibobjc class %s\n\timplements methods <%@> \n\tsuperClass %s",name,obj,class_getName([obj class]),class_getName(object_getClass(obj)),[ClassMethodNames(object_getClass(obj)) componentsJoinedByString:@", "],class_getName(class_getSuperclass(object_getClass(obj)))];printf("%s\n", [des UTF8String]);}NSArray * ClassMethodNames(Class c){NSMutableArray *arr = [NSMutableArray array];uint methodCount;Method *methodList = class_copyMethodList(c, &methodCount);for (uint i = 0; i < methodCount; i++) {[arr addObject:NSStringFromSelector(method_getName(methodList[i]))];}free(methodList);return arr;}
FFPerson的内容:
//FFPerson.h@interface FFPerson : NSObject@property(nonatomic, copy)NSString *name;@property(nonatomic, strong)NSDictionary *info;@end//FFPerson.m@implementation FFPerson@end
打印结果:
p1: <FFPerson: 0x1003017d0>NSObject class FFPersonlibobjc class FFPersonimplements methods <setInfo:, .cxx_destruct, info, name, setName:>superClass NSObjectp2: <FFPerson: 0x1003030f0>NSObject class FFPersonlibobjc class NSKVONotifying_FFPersonimplements methods <setInfo:, setName:, class, dealloc, _isKVOA>superClass FFPersonp3: <FFPerson: 0x100303110>NSObject class FFPersonlibobjc class NSKVONotifying_FFPersonimplements methods <setInfo:, setName:, class, dealloc, _isKVOA>superClass FFPersonUsing libobjc setName, p1: 0x1000012e0, p2: 0x7fff879f0957, p3: 0x7fff879f0957Using libobjc name, p1: 0x1000012c0, p2: 0x1000012c0, p3: 0x1000012c0Using libobjc setInfo, p1: 0x100001340, p2: 0x7fff879f0957, p3: 0x7fff879f0957Using libobjc info, p1: 0x100001320, p2: 0x100001320, p3: 0x100001320
1.证明obj的isa被swizzle了:
- 苹果的class方法([obj class])对外还是返回原始的class,而不是swizzle后的class(下面第二行)
- 通过runtime API,object_getClass(obj),直接通过isa取出class,发现isa指向的是NSKVONotifying_FFPerson,终于原形毕露了,也证明了isa被swizzled了。
- 这里说明一下object_getClass(obj)方法的效果和obj->isa效果一致(起码目前看起来是,后续是否这样取决于苹果爸爸的实现了),这是由于新版本runtime,苹果爸爸不再对外开放isa指针的访问了,而是封装起来了。
p2: <FFPerson: 0x1003030f0>NSObject class FFPersonlibobjc class NSKVONotifying_FFPerson
2.证明苹果通过使代理的class继承自原始的class来保证原有的继承链和相关的类信息,具体到本例,就是NSKVONotifying_FFPerson的superClass是FFPerson
第三行打印出isa指向的class为NSKVONotifying_FFPerson
第四行打印出NSKVONotifying_FFPerson的方法列表
第五行打印出NSKVONotifying_FFPerson的superClass
p2: <FFPerson: 0x1003030f0>NSObject class FFPersonlibobjc class NSKVONotifying_FFPersonimplements methods <setInfo:, setName:, class, dealloc, _isKVOA>superClass FFPerson
3.证明在代理类中,setter方法的IMP实现改变了
第二、四行,打印出getter,发现getter的IMP并没有被改变,因为没有这个必要。
第一、三行,p2,p3,不管是setName还是setInfo,都指向了同一个IMP实现,这说明,有一个统一的IMP处理KVO(实际上,是针对每个支持KVO的变量的类型来区分的,相同的变量类型具有相同的IMP)
Using libobjc setName, p1: 0x1000012e0, p2: 0x7fff879f0957, p3: 0x7fff879f0957Using libobjc name, p1: 0x1000012c0, p2: 0x1000012c0, p3: 0x1000012c0Using libobjc setInfo, p1: 0x100001340, p2: 0x7fff879f0957, p3: 0x7fff879f0957Using libobjc info, p1: 0x100001320, p2: 0x100001320, p3: 0x100001320
4.基于3,那么新的IMP实现是什么?我们调试一下打印出来,它是_NSSetObjectValueAndNotify,在Foundation.framework中。
(lldb) expr (IMP) 0x7fff879f0957(IMP) $0 = 0x00007fff879f0957 (Foundation`_NSSetObjectValueAndNotify)
如果对Foundation包执行“nm -a”命令,可以梳理出所有方法,同时聪明的你发现了,苹果KVO能够支持的property类型都在下面的方法对应上了,除了下面的类型,其它类型不支持KVO。
同时我们知道了,由于name/info属性,都是Object类型(NSString/NSDictionary),所以打印出来的IMP都是0x7fff879f0957,都指向了_NSSetObjectValueAndNotify。假如info换成int类型,你可以实验一下,地址就不同了。
0013df80 t __NSSetBoolValueAndNotify000a0480 t __NSSetCharValueAndNotify0013e120 t __NSSetDoubleValueAndNotify0013e1f0 t __NSSetFloatValueAndNotify000e3550 t __NSSetIntValueAndNotify0013e390 t __NSSetLongLongValueAndNotify0013e2c0 t __NSSetLongValueAndNotify00089df0 t __NSSetObjectValueAndNotify0013e6f0 t __NSSetPointValueAndNotify0013e7d0 t __NSSetRangeValueAndNotify0013e8b0 t __NSSetRectValueAndNotify0013e550 t __NSSetShortValueAndNotify0008ab20 t __NSSetSizeValueAndNotify0013e050 t __NSSetUnsignedCharValueAndNotify0009fcd0 t __NSSetUnsignedIntValueAndNotify0013e470 t __NSSetUnsignedLongLongValueAndNotify0009fc00 t __NSSetUnsignedLongValueAndNotify0013e620 t __NSSetUnsignedShortValueAndNotify