从自己实现isa-swizzling到说一些Runtime的内容

420 查看

请叫我背景大人

前段时间写了一篇《isa-swizzling 是什么鬼?的文章,就想说稍微实现一下isa-swizzling。然而其实实现起来非常简单,但是其中的一些Runtime内容以及一些其他的知识还是值得一讲的。

好吧,我承认我本来是想写自己实现KVO,然后我发现太长了,想说先从isa-swizzling开始。然而这并不妨碍我可能还会更一篇来说一些手动实现KVO,但是谁知道呢。如果还完全不了解isa-swizzling干了什么的同学(路人甲:谁是什么~,路人乙:你好污,巫妖王),《isa-swizzling 是什么鬼?不多说,自己看。

关门放狗,不对,是代码

其实代码挺简单的,也就这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。 最终就会变成下面这样:

isa-swizzling-UML.png

解析二:DefaultSetterForKVO

估计有些小伙伴要抓狂了,一方面是为什么static void DefaultSetterForKVO(id self, SEL _cmd, void *value)这样的函数声明,另一方面objc_superobjc_msgSendSuper这句是什么鬼。不急,在说这些之前,我们先做一个clang -rewrite-objc的操作,解析的内容是下面这些内容的.m文件:

然后我们从中截取一部分对我们目前来说有用的信息,至于里面的一些其他内容小伙伴可以从Github上找到对应的Person.cpp文件查看。

我们来尝试看看,首先可以看到上面有@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中原本我们的实现应该是: