iOS-类的底层探索(OC底层面试解析)

365 阅读3分钟

iOS - 类的加载过程(扩展/关联对象)

【面试-1】Runtime Asssociate方法关联的对象,需要在dealloc中释放?

上篇文章的_object_remove_assocations方法有介绍过关联对象释放问题,目测看是不需要不需要我们手动移除,在dealloc中自动释放

查看dealloc源码

  • dealloc

  • _objc_rootDealloc

  • rootDealloc

  • object_dispose

  • objc_destructInstance

【面试-2】方法的调用顺序

如果同名方法是普通方法,包括initialize -- 先调用分类方法

  • 因为分类的方法是在类realize之后 attach进去的,插在类的方法的前面,所以优先调用分类的方法(注意:不是分类覆盖主类!!)

  • initialize法什么时候调用? initialize方法也是主动调用,即第一次消息时调用,为了不影响整个load,可以将需要提前加载的数据写到initialize

如果同名方法是load方法 

  • 主类load,后分类load(分类之间,看编译的顺序)

【经典面试-3】 [self class]和[super class]的区别以及原理分析

示例

打印结果都是LGTeacher why?

分析:

  • 查看class方法

进入object_getClass

都是返回isa

  • 获取super和self的本质

打开终端生成.cpp文件,进入文件查看本质

本质上两个方法的接受这都是self,

  • self调用class,转换为c++中是通过objc_msgSend发送class消息。

  • super调用class,在c++中是通过objc_msgSendSuper发送class消息。

  • objc_msgSendSuper的第一个参数是一个结构体,而objc_msgSend第一个参数是id类型

进入objc_msgSendSuper方法,查看源码

搜索不到objc_msgSendSuper实现方法,在定义中可以看到objc_super结构体

搜索objc_super,进入

由上图可以知道打印的原因:

  • super是一个关键字不代表父类,它是指定方法从父类开始查找,根据isa走位图,最后也是找到NSObject中。super时编译期间调用的是objc_msgSendSuper函数,也有两个默认参数,第一个是struct objc_super * 类型,第二个是sel。而结构体中第一个属性接受者,也是self。这样调用class返回的也是self的isa

  • self调用class方法时,先在当前类中查找方法,找不到就去父类中找。最后到NSObject中。self调用时,编译期间调用的是objc_msgSend函数,函数中有两个默认参数第一个是self,第二个是sel。调用class返回的是self的isa

NSLog前打个断点,既然汇编状态:

查找运行时调用的是objc_msgSendSuper2,我们在objc源码中看看它的实现

  • 注释中解释了,objc_super的第二个属性是当前类
  • 汇编代码还是获取super class进行查找,和我们上面的分析还是一样的。

【面试-4】内存平移问题

示例

打印结果:

分析:

  • personisa指向类LGPersonperson的首地址 指向 LGPerson的首地址,我们可以通过LGPerson的内存平移找到cache,在cache中查找方法

  • [(__bridge id)kc saySomething]中的kc是来自于LGPerson 这个类,然后有一个指针kc,将其指向LGPerson的首地址

所以,person是指向LGPerson类的结构,kc也是指向LGPerson类的结构,然后都是在LGPerson中的methodList中查找方法

参考《内存地址解析

  • kc为一个 指针地址,是存在中的,栈是一个先进后出的结构,参数传入就是一个不断压栈的过程(从高向低位),验证

通过这种方式,我们可以清楚的看到栈中的内存情况,也证明了结构体属性是“反向”入栈的规则。

所以,当用桥接这种地址调用内存的过程,就是内存平移的过程。

注意:
当此方法调用方法时,为8字节,如果此时的地址少于8字节,就回导致崩溃,所以此类方法调用必须要保持字节对齐

例如: