OC底层-元类的底层探索

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

前言

在以前的学习中,咱们已经知道,类的isa指针经过&mask掩码,找到了一个与类如出一辙的类,可是两个类的地址不一样,后来通过探索得知,该类实际上是系统自动生成的,也就是元类,那么元类存在的意义究竟是什么?本篇主要讨论的重心就是于此。html

WWDC关于类的dirty Meomory和clean Memory

6F808D60B8798122EA4352A0FED7606C.png

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

588D69AE08D072AE400028028BB21173.png

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

First Subclass 、 Next Sibling Class:全部的类都会连成一个树状结构,使得你在运行时能够便利全部使用的类。缓存

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

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

1.png

因为在平时使用中,不多会有存在大量使用Methods、Properties、Protocols、Demangled Name,因此苹果对这部分作了优化,但愿可以空出更多的脏地址使用的空间,因此单独开辟了了一块结构体用于扩展(ext_t)。ide

问题遗留

问题1:

存在两个类,LGTearcher继承了LGPerson,可是在打印LGPerson信息时并无在firstSubclass中发现LGTeacher。函数

2.png

解决:

在调用了LGTeacher.class之后,能够打印出。oop

3.png

缘由:

类的调用是懒加载。学习

问题2:

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

分析:

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

解决:

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

4.png

因而调用:ro()方法

5.png

补充对协议的打印

截屏2021-06-27 下午1.09.30.png

截屏2021-06-27 下午1.09.53.png

截屏2021-06-27 下午1.10.00.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

补充注意

1.在获取bits数据强转时,必定要注意平移32位,否则强转的数据存在问题打印出来count有问题。 2.在打印元类的地址时,每每发现和类的isa的地址是同样的,可是对象的isa和类的isa不同。isa的内部存储的是类信息+其余信息、引用计数、weak表等,然后者这些都是针对于对象,与类来讲是不存在的,因此类的isa地址和元类的地址是同样的。

后记

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

相关文章
相关标签/搜索