iOS底层原理之类的原理(下)

134 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天, 点击查看活动详情

前言

上一篇中使用lldb证明了类方法在元类中,这里有个小疑问,实例方法为什么放在类中,而不是放在对象里面?为什么系统要生成一个元类呢?类方法为什么要放在元类中呢?我的简单理解就是:如果将实例方法和属性等放在对象里面,一个类可以实例化出来N多个对象,那岂不是要存N多分实例方法和属性,会造成空间的浪费,所以将实例方法放在了类中。在OC层面方法有实例方法(-)和类方法(+),但是在底层他们都是函数,是没有实例方法和类方法的称呼的,刚说了实例方法放在类中,如果也将类方法放在类中,那在底层将区分不出谁是实例方法谁是类方法,所以这是系统便生成了元类,让元类来存放类方法。

之前通过lldb进行了验证 类方法在元类中,下面我们通过api验证。

从API验证 类方法在元类中

这是DXJTeacher.h文件的定义,一个实例方法sayHello,一个类方法holiday image.png

  1. class_copyMethodList(Class, int *)

实现: image.png 调用:将元类传入

image.png 打印:

image.png 如上图所示,调用了class_copyMethodList(pClass,&count),就是将传入的pClass获取出来对应methods并将个数赋值了count,这里的methods是一个方法列表的指针,所以可以通过for循环来平移该指针,从而访问方法列表中的方法。

  1. class_getInstanceMethod(Class,sel)

实现: image.png 调用:

image.png

打印:

image.png 如上图所示,sayHello在类中找到了,holiday在元类中找到了,可以理解为class_getInstanceMethod(Class,sel) 是将传入的Class中查找是否sel函数,有则会返回对应的地址,无则是个空地址

  1. class_getClassMethod(Class,sel) 实现: image.png 调用: image.png 打印:

image.png

查看class_getClassMethod()源码发现底层也是调用class_getInstanceMethod,不同的是,class_getClassMethod()会先查看一下传入的第一个参数Class cls是否是元类,如果不是元类,会先找到传入类对应的元类cls->getMeta(),然后再元类中查找sel,如果是元类,则直接在元类中查找sel。如下图所示: image.png 也就是

  • class_getClassMethod()必然会查找的元类中的函数
  • class_getInstanceMethod()传入什么类就从什么类中找函数
  1. class_getMethodImpletation()

image.png

class_getMethodImpletation()根据传入的类去查找对应的方法实现的过程sel -> imp,最终也验证类方法 holiday在元类中找到了具体实现的imp

属性底层是如何实现赋值的?

image.png 属性赋值,本质就是如何找到属性的内存地址并赋值的过程。首先将main.m文件进行编译成c++文件,进入main.m所在的文件夹使用命令:

clang -rewrite-objc main.m -o main.cpp 执行完成之后,在main.m所在的文件夹会多出一个main.cpp文件,打开它,如下图所示:

image.png 这里看到给属性赋值时,有两种不同的方式:

  • 红线标出的, setName时调用了objc_setProperty()进行赋值,此时的name属性是用copy修饰的,想必是在llvm时期针对于copy进行了特殊的策略模式,将self属性进行了绑定
  • 蓝线标出的,setIdCard是通过内存平移的方式获取到idCard的内存地址进行赋值,self + idCard的偏移量 = idCard的内存地址

扩展之方法的types编码问题

上一篇中在探索实例方法时,发现一个有意思的types字段,一般我们想知道一个属性的编码类型,使用ivar​_get​Type​Encoding()函数即可查看,commad + shift + 0 开发文档看下该函数的描述,查看详细信息点这里,罗列了各种Code对应的含义

image.png 有了上面的基础后,分析下放到含义 image.png

  1. -[DXJTeacher hobby] 对应types@16@0:8

    @(第一个):有返回值;

    16:一共16位;

    @(第二个): 调用者;

    0:调用者从0号位置开始;

    ::方法sel;

    8:sel从第8号位置开始;

  2. -[DXJTeacher setHobby:]对应typesv24@0:8@16

    v:无返回值;

    24:一共24位;

    @(第一个): 调用者;

    0:调用者从0号位置开始;

    ::方法sel;

    8:sel从第8号位置开始;

    @(第二个): 传入的对象;

    16:传入的对象从16号位置开始