OC底层-元类的底层探索

src=http___img9.doubanio.com_view_note_l_public_p60856515.jpg&refer=http___img9.doubanio.jpeg

前言

在之前的学习中,我们已经知道,类的isa指针通过&mask掩码,找到了一个与类一模一样的类,但是两个类的地址不同,后来经过探索得知,该类其实是系统自动生成的,也就是元类,那么元类存在的意义到底是什么?本篇主要讨论的重心就是于此。

WWDC关于类的dirty Meomory和clean Memory

6F808D60B8798122EA4352A0FED7606C.png

如图:类在二进制文件表现包括了元类,父类,flags,methodCache,当然还有类本身,类对象本身含有指向元类,父类,方法缓存的指针,同时,他还有能够指向一片干净内存(clean Memory)的指针,也就是ro(只读),此部份信息不可修改。

588D69AE08D072AE400028028BB21173.png

相对的,就存在dirty Meomory。rw就是在运行时动态存在对类的内容进行修改,此部分伴随着程序运行一直存在,并且在一个类开始被使用时,系统会自动分配这块内存容量用于读写。

First Subclass 、 Next Sibling Class:所有的类都会连成一个树状结构,使得你在运行时可以便利所有使用的类。

Methods、Properties、Protocols:允许你在运行时动态的改变方法、属性(分类)。

Demangled Name:由于swift和oc是共享这片结构体,但只有swift会使用这个字段,并且只有在询问oc名称时才会使用到。

1.png

由于在平时使用中,很少会有存在大量使用Methods、Properties、Protocols、Demangled Name,所以苹果对这部分做了优化,希望能够空出更多的脏地址使用的空间,所以单独开辟了了一块结构体用于扩展(ext_t)。

问题遗留

问题1:

存在两个类,LGTearcher继承了LGPerson,但是在打印LGPerson信息时并没有在firstSubclass中发现LGTeacher。

2.png

解决:

在调用了LGTeacher.class以后,可以打印出。

3.png

原因:

类的调用是懒加载。

问题2:

类里成员变量没有打印出来

分析:

成员变量属于只读,不在properties方法中

解决:

通过搜索ro方法查到了其中的实现。

4.png

于是调用:ro()方法

5.png

总结一下对类里面数据的获取:
获取属性,是根据.properties()的方法
获取成员变量,是根据ro()
获取对象方法,是根据.methods(),取的时候要取对应的big函数,因为他没有实现打印description方法

成员变量与属性的本质区别

通过clang对文件编译获得cpp文件,可以看到底层对属性已经生成了set和get方法。

8A0E1AE4-89D0-495C-BA2B-FBDAB5CB570D.png

扩展:

关于特殊符号,可以参考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 找到了生成他的方法

98B74CB6-A369-47D0-B5AE-425E4FCE3347.png

第二步:从下而上找,哪里调用了这个方法来创建这个runtime的objc_setProperty方法呢?全局搜索getSetPropertyFn,找打了GetPropertySetFunction方法调用,只是做中间层跳转,不用管,搜索GetPropertySetFunction方法。现在我们知道是别的地方调用了这个方法从而才会有全局搜索getSetPropertyFn,但是在哪种情况下调用的,上层到底是经过了什么判断不得而知,那么现在需要知道的就是上层的处理逻辑。

CCAEAFDB-C3D9-47BC-BD21-61FDC776135A.png

第三步:沿着GetPropertySetFunction方法,可以找到调用判断的依据,有native,有GetSetProperty等等,猜想可能是不同的判断会走不同的属性或者是成员变量的赋值流程。现在只要找到给判断赋值的依据即可。

9D0570FA-3D40-4AD1-8E6B-92231D67A597.png

E24BE680-E0AC-4016-98BC-A6F7ED17B137.png

第四步:根据PropertyImplStrategy找到他的赋值以及定义的地方,可以看到copy的时候会判断加入方法,而且他都是默认,就可以猜想,copy关键字会影响到最终会不会调用GetSetProperty方法。

FB66F2EC-C712-4BFB-BA09-C34CE369716B.png

FD508F66-A92D-4383-9BE9-075425AB97DB.png

类的方法

问题1:

在之前的探索学习中,仍然缺少对类方法的打印输出。

思考:

方法不同于成员变量,方法存储在类中,防止实例化对象产生重复的方法浪费内存,同理可得,为了不浪费内存,所有的类方法存在在元类中。

方法一

经过lldb打印验证,这里就不贴图了。

方法二

通过api打印

方法三

烂苹果查看

问题2:

类的对象方法可以在类中找到,类的类方法是元类的对象方法,类的类方法与对象方法同名依然不会有问题,所以在打印的类和元类的方法列表的时候,类的对象方法元类是没有的,类的类方法在类里是打印不出来的,那为什么在元类中的“类方法”仍然可以被元类找到?

解决

通过对底层代码的简单解析,发现获取类方法的过程就是获取元类的对象方法,点进去,系统已经有了判断,如果是元类的话直接返回,也就是说元类里面可以直接查看本身的类方法了。说到这里,基本可以判断,在底层是不存在类或者对象的概念,都是对象方法,只不过取决于你从哪里取,如果是类,那就只能从 元类取,元类的话就从本身取。
6EC9C91E-5A13-482B-A34A-364D5915C5B1.png

EAB3D68F-327E-4609-9FDB-CA79676F7EE2.png

后记

第一次写,希望提出宝贵意见,互相监督,一起学习。感谢!另外,我有个大胆的想法,那就是锁住co某人的喉~~

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享