1、什么是runtime
runtime又叫运行时,将数据类型的确定由编译时推迟到了运行时 是一套比较底层的纯C语言API OC代码在运行过程中会被转成runtime的C语言代码,可以用于在程序的运行过程中动态的创建类, 动态添加、修改类的方法和属性负责消息的传递和转发查找类中的成员、属性、方法
2、Runtime的数据模型是怎样的
主要objc_object、 objc_class、 isa指针、 method_t 几个重要部分组成
objc_object:
平时我们所使用的所有对象都是ID类型的, ID类型的对象对应到runtime当中实际上代表的就是objc_object结构体 主要包含
isa_t (共用体)
关于isa指针操作 (比如通过objc_objec这个结构体获取它的isa所指向的类对象,包括类对象的isa指针指向的元类对象)
弱引用相关的方法 (比如标记一个对象是否有弱引用指针)
关联对象相关的方法 (比如这个对象我们为它设置了关联属性, 关联对象的一些方法也体现在objc_object)
内存管理 (retain/release/autoreleasePool)
objc_class:
在OC语言当中所使用的Class 对应到runtime当中就是objc_class 它也是一个结构体
objc_class继承自objc_object, objc_object中的isa指针指向objc_class类对象或元类对象
objc_class 由 superClass、 cache_t、 class_data_bits_t 3个结构体组成
superClass指针:指向的类型也是一个Class,如果是类对象则指向当前类的父类
cache_t: 主要用于消息传递当中缓存方法的查找, 对应了装满bucket_t数据结构的哈希表
class_data_bits_t: 它是对class_rw_t结构体的一层封装,主要用于表达类的基本信息,比如成员变量、属性、方法列表 包括分类的添加
class_rw_t: 代表了类相关的读写信息 是对class_ro_t的一层封装, 主要包含了
class_ro_t
properites
procotols
methods
class_ro_t:这个结构体中包含了
name
ivar
properites
procotols
methodList
isa指针:
在oc当中命名为isa_t共用体 是C++类型的空用体
指针类isa: isa的值代表Class地址
非指针型isa: isa的值部分代表Class地址
如果是对象则指向其类对象, 如果是类对象则指向其元类对象
实例 ---- isa ---- class
CLass --- isa ---- MetaClass
method_t:
1
2
3
3、cache_t的作用是什么, 具有什么特点
在消息传递的过程中用于快速查找方法执行函数,如果有一个缓存就不用到它的方法列表中去查找,可以提高消息传递效率
是可增量扩展的哈希表结构 //当我们存储的这个结构增大会逐渐的增大内存空间
是局部性原理的最佳应用
4、什么是 bucket_t (KEY IMP),如何定位bucket_t 如何进行缓存查找
bucket_t是以哈希表方式存储方法列表的, 通过key->value直接进行访问的数据结构
以SEL为key,_imp为value来存储方法
它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度
5、类对象、元类对象分别是什么
类对象与元类对象都是objc_class数据结构的,这个数据结构继承了objc_object所有他们都有isa指针
类对象存储实例方法列表等信息,
元类对象存储类表方列表法等信息
6、如何找到类对象元类对象
实例对象可以通过isa指针找到它的类对象
类对象可以通过isa指针找到它的元类对象
7、元类对象的指针指向哪里
1、任何一个元类对象它的isa指针都指向根元类对象
8、如果我们调用的类方法没有实现 但是有同名的实例方法实现会不会产生崩溃或实际调用
不会奔溃
由于根元类对象的superClass指针指向了根类对象,当我们在元类对象找类方找不到的时候会逐级向上查找 如果有同名的实例方法会调用
9、class是否是对象
Class是对象,我们称为类对象
在OC语言当中所使用的Class 对应到runtime当中就是objc_class 它也是一个结构体 ,objc_class 继承自 objc_object
10、*** 消息传递的过程
首先调用方法的时候会使用哈希方法查找 缓存中是否命中,看看缓存中是否有对应的选择器名称的方法实现,如果有就通过函数指针调用函数
如果没有则通过当前实例的isa指针查找当前类对象的方法列表,如果找到则根据函数指针进行调用 (如果方法列表已排序则使用二分查找,如果没有排序则使用遍历查找)
如果在当前类对象方法列表中没有找到就会逐级在父类方法列表中查找
如果在父类方法中一直查到根类还没有找到,就会进入消息转发流程
12、*** 什么是缓存查找,如何进行缓存查找,什么是哈希查找
在消息传递的过程中用于快速查找方法执行函数,如果有一个缓存就不用到它的方法列表中去查找,可以提高消息传递效率
缓存查找就是根据选择器因子到缓存哈希表中(cache_t)射出bucket_t在数组中的位置 找到bucket_t位置提取出imp指针返回给调用方
通过我们给定的一个值经过哈希函数的算法 算出的这个值就是bucket_t在数组中的索引位置
11、在当前类中如果查找方法
对于没有排序好的列表,采用“遍历方法”查找执行函数
对于已排序好的列表采用“二分查找”算法查找方法的对应执行函数
12、话一副消息传递流程图
13、objc_msgSend objc_msgSuperSend有什么区别
objc_msgSend:
OC的方法调用:消息机制,给方法调用者发送消息
objc_msgSend会根据在信息在对象isa指针指向的Class中寻找该SEL对应的IMP,从而完成方法的调用
任意的OC方法的调用,比如[obj aMethod]都会被翻译成objc_msgSend()
由此进入objc_msgSend执行
objc_msgSuperSend:
消息转发会调用 objc_msgSendSuper
告诉系统去父类方法列表里面去找但是调用者主体还是self
super只是一个编译器的特殊字符,并不代表父类的一个实例化对象
14、*** 消息的转发流程是怎样的
首先 实例方法的转发流程会调用:resolveInstanceMethod, 类方法的转发流程会调用resolveClassMethod (它是一个类方法返回值是BOOL 返回值是一个BOOL告诉系统我们要不要解决当前实例方法的实现 如果返回YES相当于通知系统当前消息已经处理接触了消息转发流程)
如果是NO 系统会给予我们第二次机会处理这条消息会执行 forwardingTargetForselector 这个方法(这个方法的返回值是id相当于告诉系统这个方法的调用应该由哪个对象来处理 转发对象是谁 如果指定了转发目标系统会把结果返回给转发目标并结束消息转发流程)
如果在没有转发目标的情况下,系统会给我们最后一次会 调用methodSingnatureForSelector这个方法 返回值是NSmethodSignature对象(这个对象是实际是对方法选择器返回类的类型)
如果返回了方法签名系统会返回forwardInvocation: 如果forwardInvocation能够处理则流程结束 如果NSMethodSignature返回空 则消息无法处理
15、什么是Method-Swizzling, 一般会用到哪里
Method-Swizzling 又叫方法混淆,简单说就是进行方法交换 主要用来修改selector对应的IMP实现
首先倒入runtime头文件
通过class_getClassMethod 获取方法结构体method
通过method_exchangeImplementations 进行方法交换
实现方法
新方法替换老方法
页面进出添加统计信息
数组越界 空值导致的崩溃问题
是unrecognized selector sent to class会导致APP崩溃
16、是否使用过performSelector
在动态添加方法的时候使用,一个类在编译时候没有这个方法,在运行时才产生了这个方法 在此场景下需要调用performSelector
class_addMethod(self, @selector(test), testImp, "v@:"); 方法添加
void testImp(void) {NSLog(@"test invoke");}
17、在什么场景下需要动态添加方法
如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类,添加方法解决
18、什么是动态方法解析
使用@dynamic关键字进行修饰
当我们把属性编修饰为@dynamic时代表着不需要编译器为我们生成getter setter的具体实现
在运行时具体的调用了getter setter的时候在去为它添加具体的实现,只有动态运行时语言才支持这种功能
19、编译时语言与动态运行时语言的区别, 动态进行时语言有什么优点
动态运行时语言将函数决议由编译时推迟到运行时 实际上就是在运行时为方法添加具体执行函数
编译时语言它是在编译器进行函数决议,在编译期间一个方法名称所对应的函数执行体是哪个 在具体运行过程中不能被修改
我们写代码时更具灵活性,如我们可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等
20、*** [obj foo]和 objc_msgSend()函数之间有什么关系
[obj foo]在编译器处理后就变成了 objc_msgSend函数调用 然后开始了runtime的消息传递过程
第一参数是obj
第二个参数是foo选择器
由于这个方法没有其他桉树所以objc_msgSend的餐食只有两个
21、runtime如果通过selector 找到IMP地址 (或者回答消息传递机制)
缓存是否命中
当前类方法列表是否命中 (二分查找,遍历查找)
逐级父类方法是否命中 (根据superClass a, b)
22、能否为编译后的类中添加实例变量(注意是编译后,还是动态添加)
1、不可以,因为编译之前创建的类已经完成了变量的布局, class_ro_t 在编译后无法进行修改
23、能否为动态添加的类中添加实例变量
1、可以,因为我门在动态添加的类的过程中只要在它调用注册类队之前完成实例变量的添加就可以实现
24、***runtime 如何实现 weak 属性
weak的特点:
weak策略表明该属性定义了一种“非拥有关系” (nonowning relationship)
为这种属性设置新值时,设置方法既不保留新值,也不释放旧值
此特质同assign类似;然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)
runtime 如何实现 weak 变量的自动置nil:
runtime 对注册的类会进行布局,对于weak对象会放入一个hash表中
用weak指向的对象内存地址作为key,当此对象的引用计数为0的时候会dealloc
假如weak指向的对象内存地址是a,那么就会以a为键,在这个weak表中搜索,找到所有以a为键的weak对象从而设置为 nil
26、runtime怎么添加属性、方法等
1、class_addIvar、class_addMethod、class_addProperty、class_addProtocol、class_replaceProperty
27、_objc_msgForward函数是做什么的?直接调用它将会发生什么
_objc_msgForward是IMP类型,用于消息转发的,当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发
28、*** 消息传递与函数调用之间的区别, OC是怎么调用函数的
可以随时对一个对象传递任何消息,而不需要在编译的时候声明这些方法
消息传递:运行时所执行的代码由运行时环境决定,消息传递可以随时对一个对象传递任何消息,并且不需要在编译的时候去声明这些方法
函数调用:运行时所执行的代码由编译器决定 函数如果没有声明就去调用的话,会造成编译失败,而消息就不会这样