前言
在之前的学习中,我们已经知道,类的isa指针通过&mask掩码,找到了一个与类一模一样的类,但是两个类的地址不同,后来经过探索得知,该类其实是系统自动生成的,也就是元类,那么元类存在的意义到底是什么?本篇主要讨论的重心就是于此。
WWDC关于类的dirty Meomory和clean Memory
如图:类在二进制文件表现包括了元类,父类,flags,methodCache,当然还有类本身,类对象本身含有指向元类,父类,方法缓存的指针,同时,他还有能够指向一片干净内存(clean Memory)的指针,也就是ro(只读),此部份信息不可修改。
相对的,就存在dirty Meomory。rw就是在运行时动态存在对类的内容进行修改,此部分伴随着程序运行一直存在,并且在一个类开始被使用时,系统会自动分配这块内存容量用于读写。
First Subclass 、 Next Sibling Class:所有的类都会连成一个树状结构,使得你在运行时可以便利所有使用的类。
Methods、Properties、Protocols:允许你在运行时动态的改变方法、属性(分类)。
Demangled Name:由于swift和oc是共享这片结构体,但只有swift会使用这个字段,并且只有在询问oc名称时才会使用到。
由于在平时使用中,很少会有存在大量使用Methods、Properties、Protocols、Demangled Name,所以苹果对这部分做了优化,希望能够空出更多的脏地址使用的空间,所以单独开辟了了一块结构体用于扩展(ext_t)。
问题遗留
问题1:
存在两个类,LGTearcher继承了LGPerson,但是在打印LGPerson信息时并没有在firstSubclass中发现LGTeacher。
解决:
在调用了LGTeacher.class以后,可以打印出。
原因:
类的调用是懒加载。
问题2:
类里成员变量没有打印出来
分析:
成员变量属于只读,不在properties方法中
解决:
通过搜索ro方法查到了其中的实现。
于是调用:ro()方法
总结一下对类里面数据的获取:
获取属性,是根据.properties()的方法
获取成员变量,是根据ro()
获取对象方法,是根据.methods(),取的时候要取对应的big函数,因为他没有实现打印description方法
成员变量与属性的本质区别
通过clang对文件编译获得cpp文件,可以看到底层对属性已经生成了set和get方法。
扩展:
关于特殊符号,可以参考ivar_getTypeEncoding获取typeEncoding,先附上官方文档地址:
typeEncoding
V:返回空(可以看到该方法是set方法)
@:id (可以看到当前是get方法,由返回值)
16/24:表示一共占用的字节
@ : 参数 id self
0:从0号位置开始
: : SEL
8 : 从8号位置开始
16 = 8 + 8
24 = 16 + 8
类的结构
问题
在编译后的cpp文件中,我们发现属性有的是objc_setProperty方法,有的是内存平移,也就是说实现的方式不同。
思考:
在给类的属性赋值时,oc会根据你的属性找到对应的set 和 get 方法,但是如何通过定义好的属性去生成对应的set 和 get方法呢?在上层来看,set就是赋值,get就是拿值,而在于底层来看,不管是set还是get,都是基于cmd的方法,只是名字不同而已,所以可以进行统一的封装处理。但是从上层到底层无法直接通信,个人理解是需要一个中间层去把上层的数据处理好,给底层使用,类似于用一个通用的方法去统一处理。
过程猜想:类在加载的时候可以生成ivars,根据ivars的sel去找到对应的imp,但是此时imp还没有实现,那么目前oc中是用了objc_setProperty重定向的方式。结合最开学的分类的一些知识可以知道,我们现在的赋值操作并不会改变这个类,所以objc_setProperty自然也不会在运行时产生,另外,如果在运行时再去操作对系统压力太大(objc源码),而且如果还没有实现,那么在运行时去查找很容易出错,不太容易能查到对方的方法。由此可以通过对llvm的查找得出他在底层的实现原理。
探索:
第一步:打开了LLVM以后,根据objc_setProperty 找到了生成他的方法
第二步:从下而上找,哪里调用了这个方法来创建这个runtime的objc_setProperty方法呢?全局搜索getSetPropertyFn,找打了GetPropertySetFunction方法调用,只是做中间层跳转,不用管,搜索GetPropertySetFunction方法。现在我们知道是别的地方调用了这个方法从而才会有全局搜索getSetPropertyFn,但是在哪种情况下调用的,上层到底是经过了什么判断不得而知,那么现在需要知道的就是上层的处理逻辑。
第三步:沿着GetPropertySetFunction方法,可以找到调用判断的依据,有native,有GetSetProperty等等,猜想可能是不同的判断会走不同的属性或者是成员变量的赋值流程。现在只要找到给判断赋值的依据即可。
第四步:根据PropertyImplStrategy找到他的赋值以及定义的地方,可以看到copy的时候会判断加入方法,而且他都是默认,就可以猜想,copy关键字会影响到最终会不会调用GetSetProperty方法。
类的方法
问题1:
在之前的探索学习中,仍然缺少对类方法的打印输出。
思考:
方法不同于成员变量,方法存储在类中,防止实例化对象产生重复的方法浪费内存,同理可得,为了不浪费内存,所有的类方法存在在元类中。
方法一
经过lldb打印验证,这里就不贴图了。
方法二
通过api打印
方法三
烂苹果查看
问题2:
类的对象方法可以在类中找到,类的类方法是元类的对象方法,类的类方法与对象方法同名依然不会有问题,所以在打印的类和元类的方法列表的时候,类的对象方法元类是没有的,类的类方法在类里是打印不出来的,那为什么在元类中的“类方法”仍然可以被元类找到?
解决
通过对底层代码的简单解析,发现获取类方法的过程就是获取元类的对象方法,点进去,系统已经有了判断,如果是元类的话直接返回,也就是说元类里面可以直接查看本身的类方法了。说到这里,基本可以判断,在底层是不存在类或者对象的概念,都是对象方法,只不过取决于你从哪里取,如果是类,那就只能从 元类取,元类的话就从本身取。
后记
第一次写,希望提出宝贵意见,互相监督,一起学习。感谢!另外,我有个大胆的想法,那就是锁住co某人的喉~~