请叫我背景大人
前段时间写了一篇《isa-swizzling 是什么鬼?》的文章,就想说稍微实现一下isa-swizzling
。然而其实实现起来非常简单,但是其中的一些Runtime内容以及一些其他的知识还是值得一讲的。
好吧,我承认我本来是想写自己实现KVO,然后我发现太长了,想说先从isa-swizzling
开始。然而这并不妨碍我可能还会更一篇来说一些手动实现KVO,但是谁知道呢。如果还完全不了解isa-swizzling
干了什么的同学(路人甲:谁是什么~,路人乙:你好污,巫妖王),《isa-swizzling 是什么鬼?》不多说,自己看。
关门放狗,不对,是代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
static void DefaultSetterForKVO(id self, SEL _cmd, void *value) { NSString *setterName = NSStringFromSelector(_cmd); NSString *key = [self obtainGetterFromSetter:setterName]; [self willChangeValueForKey:key]; Class subCls = object_getClass(self); Class supCls = class_getSuperclass(subCls); struct objc_super superInfo = { self, supCls }; ((void (*) (void * , SEL, ...))objc_msgSendSuper)(&superInfo, _cmd, value); [self didChangeValueForKey:key]; } - (void)py_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { // isa-swizzling implement NSString *newName = [@"PYKVONotifying_" stringByAppendingString:NSStringFromClass(object_getClass(self))]; NSString *setterName = [[@"set" stringByAppendingString:[[[keyPath uppercaseString] substringToIndex:1] stringByAppendingString:[keyPath substringFromIndex:1]]] stringByAppendingString:@":"]; Class subCls = objc_allocateClassPair(object_getClass(self), [newName UTF8String], 0); class_addMethod(subCls, NSSelectorFromString(setterName), (IMP)DefaultSetterForKVO, "v@:@"); objc_registerClassPair(subCls); object_setClass(self, subCls); } |
其实代码挺简单的,也就这20+行代码,但是里面有的一些东西还是可以看看的。
解析一:addObserver:forKeyPath:options:context:
上面是重写的addObserver:forKeyPath:options:context:
一小部分,但是这里面的知识点也可圈可点。我们先从addObserver
开始看吧。
前两句没什么好说的,其实就是根据原有类名以及keyPath
来获得新类名以及需要重写的方法setter
方法(不对啊,keyPath可能带’.’之类的啊),保安有人捣乱。好吧,其实这里就是简单的忽略keyPath,假设不存在那些特殊情况。
接下来剩下的三行代码分别是动态生成类以及以及添加新的方法以及注册类。我们说说最后一个调用object_setClass
的作用,为什么要这么做呢?我们都说了是isa-swizzling
了当然第一个作用就是通过这个方法来“偷梁换柱”,将isa
换成我们新产生的这个类,那么为什么有必要将isa
指向新的类呢?其实主要目的是:我们都知道objc_class
中保存着我们实例方法的objc_method_list
,而一个类的isa
才是指向这个类的Class,也就是objc_class
。也就是说Objc在查找一个类的实现方法是通过isa
去查找具体Class的objc_method_list
。 最终就会变成下面这样:
解析二:DefaultSetterForKVO
估计有些小伙伴要抓狂了,一方面是为什么static void DefaultSetterForKVO(id self, SEL _cmd, void *value)
这样的函数声明,另一方面objc_super
和objc_msgSendSuper
这句是什么鬼。不急,在说这些之前,我们先做一个clang -rewrite-objc
的操作,解析的内容是下面这些内容的.m文件
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// Person.h文件 @interface Person : NSObject @property (nonatomic, copy) NSString *id; @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSUInteger age; @end // Person.m文件 @implementation Person - (void)test { [super description]; } @end |
然后我们从中截取一部分对我们目前来说有用的信息,至于里面的一些其他内容小伙伴可以从Github上找到对应的Person.cpp文件查看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
struct __rw_objc_super { struct objc_object *object; struct objc_object *superClass; __rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} }; // @implementation Person static void _I_Person_test(Person * self, SEL _cmd) { ((NSString *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}, sel_registerName("description")); } static NSString * _I_Person_id(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_id)); } extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool); static void _I_Person_setId_(Person * self, SEL _cmd, NSString *id) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _id), (id)id, 0, 1); } static NSString * _I_Person_name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_name)); } static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); } static NSUInteger _I_Person_age(Person * self, SEL _cmd) { return (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_age)); } static void _I_Person_setAge_(Person * self, SEL _cmd, NSUInteger age) { (*(NSUInteger *)((char *)self + OBJC_IVAR_$_Person$_age)) = age; } // @end static struct /*_method_list_t*/ { unsigned int entsize; // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[7]; } _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 7, {{(struct objc_selector *)"test", "v16@0:8", (void *)_I_Person_test}, {(struct objc_selector *)"id", "@16@0:8", (void *)_I_Person_id}, {(struct objc_selector *)"setId:", "v24@0:8@16", (void *)_I_Person_setId_}, {(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name}, {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_}, {(struct objc_selector *)"age", "Q16@0:8", (void *)_I_Person_age}, {(struct objc_selector *)"setAge:", "v24@0:8Q16", (void *)_I_Person_setAge_}} }; |
我们来尝试看看,首先可以看到上面有@implementation Person
和@end
被注释了,在机上里面的内容我们可以看出来这一部分是Person.m
文件转换成C之后的代码。而根据下面的一个struct,我们可以看得出来,其实clang为我们分装成具体objc_method_list
方面之后好查找。
同时我们也可以看到原本我们的方法都被_I_Person_方法名(Person *self, SEL _cmd, [其他参数...])
的形式。而这个形式正是我们上面的DefaultSetterForKVO
的形式,也就是说我们可以通过这样写直接转换成- (void)DefaultSetterForKVO:(void *)value
的形式, 只是我们直接将其转换成C的形式来实现。
然后我们来看看test
方法的实现,看起来好像很复杂,我们一点点分解,先看这一段(__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("Person"))}
,其实这一段的作用就是创建一个最开头看到的__rw_objc_super
的struct,而这个__rw_objc_super
就和我们上面objc_super
是等价的。然后就是调用objc_msgSendSuper
这个C方法,这句话很纸老虎,其实它很多内容都是在进行类型转换。那么通过这个[super description]
可以看得出来,这个其实就是调用了objc_msgSendSuper
,然后是需要创建一个objc_super
的struct作为第一个参数,而第二个参数是selector。那么根据我们objc_msgSendSuper
的了解,如果之后函数还有其他参数就应该添加在之后。
从这里也能看出@我就叫Sunny怎么了博客中神经病院objc runtime入院考试中的第一题的解释中为什么
reveiver
还是self
的答案。
那么在DefaultSetterForKVO
中原本我们的实现应该是:
1 2 3 |
[self willChangeValueForKey:key]; |