@blueGhost
2017-06-30T17:16:51.000000Z
字数 6329
阅读 1441
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 FFPerson
libobjc class FFPerson
implements methods <setInfo:, .cxx_destruct, info, name, setName:>
superClass NSObject
p2: <FFPerson: 0x1003030f0>
NSObject class FFPerson
libobjc class NSKVONotifying_FFPerson
implements methods <setInfo:, setName:, class, dealloc, _isKVOA>
superClass FFPerson
p3: <FFPerson: 0x100303110>
NSObject class FFPerson
libobjc class NSKVONotifying_FFPerson
implements methods <setInfo:, setName:, class, dealloc, _isKVOA>
superClass FFPerson
Using libobjc setName, p1: 0x1000012e0, p2: 0x7fff879f0957, p3: 0x7fff879f0957
Using libobjc name, p1: 0x1000012c0, p2: 0x1000012c0, p3: 0x1000012c0
Using libobjc setInfo, p1: 0x100001340, p2: 0x7fff879f0957, p3: 0x7fff879f0957
Using 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 FFPerson
libobjc class NSKVONotifying_FFPerson
2.证明苹果通过使代理的class继承自原始的class来保证原有的继承链和相关的类信息,具体到本例,就是NSKVONotifying_FFPerson的superClass是FFPerson
第三行打印出isa指向的class为NSKVONotifying_FFPerson
第四行打印出NSKVONotifying_FFPerson的方法列表
第五行打印出NSKVONotifying_FFPerson的superClass
p2: <FFPerson: 0x1003030f0>
NSObject class FFPerson
libobjc class NSKVONotifying_FFPerson
implements 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: 0x7fff879f0957
Using libobjc name, p1: 0x1000012c0, p2: 0x1000012c0, p3: 0x1000012c0
Using libobjc setInfo, p1: 0x100001340, p2: 0x7fff879f0957, p3: 0x7fff879f0957
Using 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 __NSSetBoolValueAndNotify
000a0480 t __NSSetCharValueAndNotify
0013e120 t __NSSetDoubleValueAndNotify
0013e1f0 t __NSSetFloatValueAndNotify
000e3550 t __NSSetIntValueAndNotify
0013e390 t __NSSetLongLongValueAndNotify
0013e2c0 t __NSSetLongValueAndNotify
00089df0 t __NSSetObjectValueAndNotify
0013e6f0 t __NSSetPointValueAndNotify
0013e7d0 t __NSSetRangeValueAndNotify
0013e8b0 t __NSSetRectValueAndNotify
0013e550 t __NSSetShortValueAndNotify
0008ab20 t __NSSetSizeValueAndNotify
0013e050 t __NSSetUnsignedCharValueAndNotify
0009fcd0 t __NSSetUnsignedIntValueAndNotify
0013e470 t __NSSetUnsignedLongLongValueAndNotify
0009fc00 t __NSSetUnsignedLongValueAndNotify
0013e620 t __NSSetUnsignedShortValueAndNotify