携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天, 点击查看活动详情
前言
上一篇中使用lldb证明了类方法在元类中,这里有个小疑问,实例方法为什么放在类中,而不是放在对象里面?为什么系统要生成一个元类呢?类方法为什么要放在元类中呢?我的简单理解就是:如果将实例方法和属性等放在对象里面,一个类可以实例化出来N多个对象,那岂不是要存N多分实例方法和属性,会造成空间的浪费,所以将实例方法放在了类中。在OC层面方法有实例方法(-)和类方法(+),但是在底层他们都是函数,是没有实例方法和类方法的称呼的,刚说了实例方法放在类中,如果也将类方法放在类中,那在底层将区分不出谁是实例方法谁是类方法,所以这是系统便生成了元类,让元类来存放类方法。
之前通过lldb进行了验证 类方法在元类中,下面我们通过api验证。
从API验证 类方法在元类中
这是DXJTeacher.h文件的定义,一个实例方法sayHello,一个类方法holiday
class_copyMethodList(Class, int *)
实现:
调用:将元类传入
打印:
如上图所示,调用了
class_copyMethodList(pClass,&count),就是将传入的pClass获取出来对应methods并将个数赋值了count,这里的methods是一个方法列表的指针,所以可以通过for循环来平移该指针,从而访问方法列表中的方法。
class_getInstanceMethod(Class,sel)
实现:
调用:
打印:
如上图所示,
sayHello在类中找到了,holiday在元类中找到了,可以理解为class_getInstanceMethod(Class,sel) 是将传入的Class中查找是否sel函数,有则会返回对应的地址,无则是个空地址
class_getClassMethod(Class,sel)实现:调用:
打印:
查看class_getClassMethod()源码发现底层也是调用class_getInstanceMethod,不同的是,class_getClassMethod()会先查看一下传入的第一个参数Class cls是否是元类,如果不是元类,会先找到传入类对应的元类cls->getMeta(),然后再元类中查找sel,如果是元类,则直接在元类中查找sel。如下图所示:
也就是
class_getClassMethod()必然会查找的元类中的函数class_getInstanceMethod()传入什么类就从什么类中找函数
class_getMethodImpletation()
class_getMethodImpletation()根据传入的类去查找对应的方法实现的过程sel -> imp,最终也验证类方法 holiday在元类中找到了具体实现的imp
属性底层是如何实现赋值的?
属性赋值,本质就是如何找到
属性的内存地址并赋值的过程。首先将main.m文件进行编译成c++文件,进入main.m所在的文件夹使用命令:
clang -rewrite-objc main.m -o main.cpp
执行完成之后,在main.m所在的文件夹会多出一个main.cpp文件,打开它,如下图所示:
这里看到给属性赋值时,有两种不同的方式:
- 红线标出的,
setName时调用了objc_setProperty()进行赋值,此时的name属性是用copy修饰的,想必是在llvm时期针对于copy进行了特殊的策略模式,将self和属性进行了绑定 - 蓝线标出的,
setIdCard是通过内存平移的方式获取到idCard的内存地址进行赋值,self + idCard的偏移量 = idCard的内存地址
扩展之方法的types编码问题
上一篇中在探索实例方法时,发现一个有意思的types字段,一般我们想知道一个属性的编码类型,使用ivar_getTypeEncoding()函数即可查看,commad + shift + 0 开发文档看下该函数的描述,查看详细信息点这里,罗列了各种Code对应的含义
有了上面的基础后,分析下放到含义
-
-[DXJTeacher hobby]对应types@16@0:8@(第一个):有返回值;16:一共16位;@(第二个): 调用者;0:调用者从0号位置开始;::方法sel;8:sel从第8号位置开始; -
-[DXJTeacher setHobby:]对应typesv24@0:8@16v:无返回值;24:一共24位;@(第一个): 调用者;0:调用者从0号位置开始;::方法sel;8:sel从第8号位置开始;@(第二个): 传入的对象;16:传入的对象从16号位置开始