关于Runtime的一些常见面试题
objc实例对象的isa指针指向什么?有什么作用?
实例对象的isa指向他的类对象,实例对象可以从他的类对象上找到实例对象所包含的实例方法、属性、代理等信息。
类对象的isa指向他的元类对象,类对象可以从元类对象中找到类对象所包含的类方法等信息。
一个 NSObject 对象占用多少内存空间
在XCode中创建一个NSObject对象,按住Command查看NSObject的定义的时候,就会发现,一个NSObject对象的大小就是一个isa指针的大小。一个isa的指针应该是8字节,但是实际使用malloc_size()去验证的时候却都是16字节。这是因为,对象在分配内存空间的时候,会进行内存对齐,在iOS中,分配的内存空间都是16字节的整数倍,所以这里是分配的16字节。
说一下对 class_rw_t 的理解?
rw 代表可读可写。
ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中
说一下对 class_ro_t 的理解?
存储了当前类在编译期就已经确定的属性、方法以及遵循的协议。
说一下对 isa 指针的理解
isa 可以简单的用一句话来代替,<是一种>
1 实例对象 isa 指向类对象
2 类对象指 isa 向元类对象
3 元类对象的 isa 指向元类的基类
isa 有两种类型
1 纯指针,指向内存地址
2 NON_POINTER_ISA,除了内存地址,还存有一些其他信息
从上面的代码可以看出,isa_t对象除了包含内存地址还包含了一些扩展信息
说一下 Runtime 的方法缓存?存储的形式、数据结构以及查找的过程?
cache_t 增量扩展的哈希表结构。哈希表内部存储的 bucket_t。
bucket_t 中存储的是 SEL 和 IMP 的键值对。
使用runtime Associate方法关联的对象,需要在主对象dealloc的时候释放么?
无论在MRC下还是ARC下均不需要,被关联的对象在生命周期内要比对象本身释放的晚很多,它们会在被NSObject的dealloc调用的object_dispose()方法中释放。
实例对象的数据结构?
NSObject在Runtime中只有isa一个私有属性,指向类对象的内存地址,剩下的全是public方法
什么是method swizzling
OC中的函数调用机制叫消息发送机制,是向一个对象的方法发送消息,去调用这个方法,而查找这个方法的唯一依据就是selector的名字,根据selector找到对应的方法实现(IMP),对其调用。而Runtime提供了一种可能,就是修改selector和IMP的对应关系,从而实现方法调用的交换。
这种改变对应关系的API有三个:
1 method_exchangeImplementations 直接交换两个方法的实现
2 class_replaceMethod 替换方法的实现
3 method_setImplementation 直接设置某个方法的IMP
能否向编译后得到的类中增加实例变量?能否向运行时创建的类中添加实例变量? 为什么?
不能向编译后得到的类中增加实例变量;
能向运行时创建的类中添加实例变量;
1.因为编译后的类已经注册在runtime中,类结构体中的objc_ivar_list实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime会调用 class_setVarLayout或class_setWeakIvarLayout来处理strong和weak引用.所以不能向存在的类中添加实例变量。
2.运行时创建的类是可以添加实例变量,调用 class_addIvar 函数. 但是的在调用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.
类对象的数据结构?
类对象就是 objc_class。
它的结构相对丰富一些。继承自 objc_object 结构体,所以包含 isa 指针 isa:指向元类
superClass: 指向父类
Cache: 方法的缓存列表
data: 顾名思义,就是数据。是一个被封装好的 class_rw_t 。
runtime 如何通过 selector 找到对应的 IMP 地址?
每一个类对象中都一个方法列表,方法列表中记录着方法的名称,方法实现,以及参数类型,其实 selector 本质 就是方法名称,通过这个方法名称就可以在方法列表中找到对应的方法实现.
runtime 如何实现 weak 变量的自动置 nil?知道 SideTable 吗?
runtime 对注册的类会进行布局,对于 weak 修饰的对象会放入一个 hash 表中。 用 weak 指向的对象内 存地址作为 key,当此对象的引用计数为 0 的时候会 dealloc,假如 weak 指向的对象内存地址是 a,那么就 会以 a 为键, 在这个 weak 表中搜索,找到所有以 a 为键的 weak 对象,从而设置为 nil。
SideTable 这个结构体,我给他起名引用计数和弱引用依赖表,因为它主要用于管理对象的引用计数和 weak 表。在 NSObject.mm 中声明其数据结构:
1 | struct SideTable { |
对于 slock 和 refcnts 两个成员不用多说,第一个是为了防止竞争选择的自旋锁,第二个是协助对象的 isa 指针的 extra_rc 共同引用计数的变量(对于对象结果,在今后的文中提到)。这里主要看 weak 全局 hash 表的结构与作用。
当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?
当释放对象时,其基本流程如下:
1.调用 objc_release
2.因为对象的引用计数为0,所以执行 dealloc
3.在dealloc中,调用了_objc_rootDealloc函数
4.在_objc_rootDealloc中,调用了object_disponse函数
5.调用objc_destructinstance函数
6.最后调用objc_clear_deallocating
对象被释放时调用的 objc_clear_deallocating 函数:
1.从 weak 表中获取废弃对象的地址为键值的记录
2.将包含在记录中的所有附有 weak 修饰符变量的地址,赋值为 nil
3.将 weak 表中该记录删除
4.从引用计数表中删除废弃对象的地址为键值的记录
十六、objc 中向一个 nil 对象发送消息将会发生什么?
如果向一个 nil 对象发送消息,首先在寻找对象的 isa 指针时就是 0 地址返回了,所以不会出现任何错误。 也不会崩溃。
isKindOfClass 与 isMemberOfClass
1、isKindOfClass可用于判断对象是否是一个类的成员,或者是该派生类的成员
2、isMemberOfClass可用于判断对象是否是当前类
一句话就是, isKindOfClass 判断是否是自己或者父类, isMemberOfClass 判断是否是自己
Category 在编译过后,是在什么时机与原有的类合并到一起的?
- 程序启动后,通过编译之后,Runtime 会进行初始化,调用 _objc_init。
- 然后会map_images.
- 接下来调用map_images_nolock.
- 再然后就是read_images,这个方法会读取所有的类的相关信息。
- 最后是调用reMethodizeClass:,这个方法是重新方法化的意思。
- 在reMethodizeClass: 方法内部会调用 attachCategories: ,这个方法会传入 Class ,会将方法列表,协议列表等与原有的类合并。最后加入到 class_rw_t 结构体
Category 的实现原理?
Category是被添加在了 class_rw_t 的对应结构里。
Category 实际上是Category_t的结构体,在运行时,新添加的方法,都被以倒序插入到原有方法列表的最前面,所以不同的Category,添加了同一个方法,执行的实际上是最后一个。
拿方法列表举例,实际上是一个二维的数组。
Category 如果翻看源码的话就会知道实际上是一个 _catrgory_t 的结构体。
–
例如我们在程序中写了一个 Nsobject+Tools 的分类,那么被编译为 C++ 之后,实际上是:
Category在刚刚编译完的时候,和原来的类是分开的,只有在程序运行起来后,通过 Runtime ,Category和原来的类才会合并到一起。
mememove,memcpy:这俩方法是位移、复制,简单理解就是原有的方法移动到最后,同时新开辟的空间,把前面的位置留给分类,然后分类中的方法,按照倒序依次插入,可以得出的结论就就是,越晚参与编译 的分类,里面的方法才是生效的那个。
[self class] 与 [super class]
下面的参考文档最后的23题面试题,简而言之就是:
如果是在实例对象里面调用,这两个结果都是son。
如果是直接类方法调用,就是son和father