前言
- 探索了一阵子的OC底层原理之后,我们通过下面几个面试题复习一下。
目录
- Runtime是什么?
- 方法的本质,sel是什么?IMP是什么?两者之间的关系又是什么?
- 方法的调用顺序
- Runtime是如何实现weak的,为什么可以自动置nil
- 能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量
- Runtime Asssociate方法关联的对象,需要在dealloc中释放?
- [self class]和[super class]的区别?
- 内存平移问题
一、Runtime是什么?
- runtime是由C和C++汇编实现的一套API,为OC语言加入了 面向对象、以及运行时的功能
- 平时编写的OC代码,在程序运行的过程中,其实最终会转换成runtime的C语言代码, runtime是OC的幕后工作者
- 运行时是指将数据类型的确定由编译时 推迟到了 运行时
举例:category分类,添加属性是无用的,调用属性会crash,但是通过runtime动态关联对象,重写setter、getter方法的方式就可以实现分类的属性。
二、方法的本质,sel是什么?IMP是什么?两者之间的关系又是什么?
-
方法的本质:发送消息,消息会有以下几个流程
快速查找(objc_msgSend) - cache_t缓存消息中查找
慢速查找 - 递归自己|父类 - lookUpImpOrForward
查找不到消息:动态方法解析 - resolveInstanceMethod
消息快速转发 - forwardingTargetForSelector
消息慢速转发 - methodSignatureForSelector & forwardInvocation
sel是方法编号 - 在read_images期间就编译进了内存 ,sel 相当于 一本书的目录title
imp是函数实现指针 ,找imp就是找函数的过程 ,imp 相当于 书本的页码
-
查找具体的函数就是想看这本书具体篇章的内容
- 1、首先知道想看什么,即目录 title - sel
- 2、根据目录找到对应的页码 - imp
- 3、通过页码去翻到具体的内容
三、方法的调用顺序
类的方法 和 分类方法 重名,如果调用,是什么情况?
-
如果同名方法是普通方法,包括initialize -- 先调用分类方法
- 因为分类的方法是在类realize之后 attach进去的,插在类的方法的前面,所以优先调用分类的方法(注意:不是分类覆盖主类!!)
- initialize方法什么时候调用? initialize方法也是主动调用,即第一次消息时调用,为了不影响整个load,可以将需要提前加载的数据写到initialize中
如果同名方法是load方法 -- 先 主类load,后分类load(分类之间,看编译的顺序)
四、 Runtime是如何实现weak的,为什么可以自动置nil
初步回答:
runtime 对注册的类会进行布局,对于 weak 修饰的对象会放入一个 hash 表(弱引用表)中。 用 weak 指向的对象内存地址作为key,当此对象的引用计数为0的时候会 dealloc,假如 weak 指向的对象内存地址是a,那么就会以a为键, 在这个 weak表中搜索,找到所有以a为键的 weak 对象,从而设置为 nil。-
细致回答:
- 1.初始化时:runtime会调用objc_initWeak函数,初始化一个新的weak指针指向对象的地址。
- 2.添加引用时:objc_initWeak函数会调用objc_storeWeak() 函数, objc_storeWeak()的作用是更新指针指向,创建对应的弱引用表。
- 3.释放时,调用clearDeallocating函数。clearDeallocating函数首先根据对象地址获取所有weak 指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
当weak引用指向的对象被释放时,如何去处理weak指针?
1、调用objc_release
2、因为对象的引用计数为0,所以执行dealloc
3、在dealloc中,调用了_objc_rootDealloc函数
4、在_objc_rootDealloc中,调用了object_dispose函数
5、调用objc_destructInstance
6、最后调用objc_clear_deallocating简单来说:
a. 从weak表中获取被释放对象的地址为键值的记录
b. 将包含在记录中的所有附有 weak修饰符变量的地址,赋值为 nil
c. 将weak表中该记录删除
d. 从引用计数表中删除废弃对象的地址为键值的记录
五、能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?
- 问题一: 不可以。 因为编译好的实例变量存放的位置在ro,一旦编译完成,内存结构就完全确定了,无法修改。
- 问题二:在register注册前,可以添加。但是调用运行时register注册后,就完成了内存的注入,内存结构确定了,无法修改。
六、Runtime Asssociate方法关联的对象,需要在dealloc中释放?
- 不会,因为在dealloc中会自动释放掉关联的对象,在关联对象的时候,isa中记录了是否有关联对象。通过dealloc 释放。执行如下:objc_object::rootDealloc() -> object_dispose()->objc_destructInstance() ,在objc_destructInstance() 这里面会根据isa指针下的has_assoc标记来判断是否有关联对象,如果有会自动释放的。
七、 [self class]和[super class]的区别?
- 主要是他们方法查找顺序不一样。[super class]中,其中super 是语法的 关键字,可以通过clang 看super的本质,这是编译时的底层源码,其中第一个参数是消息接收者,是一个__rw_objc_super结构
- [self class]就是发送消息 objc_msgSend,消息接收者是self,方法编号 class
- [super class] 本质就是objc_msgSendSuper,消息的接收者还是 self,方法编号 class,在运行时,底层调用的是_objc_msgSendSuper2
- 实际运行时,[super class]在汇编层执行直接从superclass父类开始搜索,节约了一轮查找资源, objc_msgSendSuper2 会更快,直接跳过self的查找