iOS的动态运行时体现在:
- 运行时根据对象isa指针来实现查找属性、方法等
- 消息传递,运行时根据方法名
SEL
查找方法实现IMP
,或进入消息转发流程 - Method-Swizzling,方法名
SEL
与方法实现IMP
交换 - 动态添加方法
- 动态方法解析
- 运行时决议
消息传递
实例方法存储在类对象中,静态方法存储在元类对象中。
调用方法,会在编译阶段编译成如下代码:
[self description];
objc_msgSend(self, @selector(description)) # 编译后的代码
[NSObject description];
objc_msgSend([NSObject class], @selector(description)) # 编译后的代码
消息传递过程分为三个阶段:
- 消息发送
- 动态方法解析
- 消息转发
以调用实例方法为例,运行时,根据实例对象的isa
指针,找到类对象。类对象结构如下:
# 类对象结构
struct objc_class {
Class isa; # 指向元类对象
Class superClass; # 指向父级类对象
cache_t cache; # 方法缓存
class_data_bits_t bits; # 用于获取具体的类信息
}
类对象结构体的的bits
跟实例对象的位域存储数据FAST_DATA_MASK
进行&
操作,即可得到结构体class_rw_t
,class_rw_t
中存储着实例方法列表、属性列表和协议列表。结构如下:
struct class_rw_t {
// ...省略其他值
# 方法列表 二维数组 [method_list_t, method_list_t, ...] , method_list_t数组包含了method_t
method_list_t *methods;
# 属性列表 二维数组
property_list_t *properties;
# 协议列表 二维数组
const protocol_list_t *protocols;
}
实例方法列表中的method_list_t
保存的method_t
结构如下:
# method_t是对方法(函数)的封装
struct method_t {
SEL name; # 函数名
const char *types; # 编码(返回值类型、参数类型)
IMP imp; # 指向函数地址的指针
}
通过SEL
查找方法的IMP
顺序是:
-
先在当前类中查找
- 先查找方法缓存列表是否命中:通过给定的方法选择器
SEL
,通过一个函数(y = f(x)
),经过哈希查找来映射出bucket_t
在数组中的索引位置,如果方法缓存没有命中,再查找方法列表 - 对于已经排序好的,采用二分查找算法,查找对应方法的执行函数
- 对于未排序好的,采用一般遍历查找算法,查找对应方法的执行函数
- 先查找方法缓存列表是否命中:通过给定的方法选择器
-
如果当前类中没有找到,根据
superClass
指针去父级类中查找- 先查找方法缓存列表是否命中
- 方法缓存未命中,再查找方法列表是否命中
-
如果逐级父类都没有找到该方法,进入动态方法解析流程
- 对于类方法,可在
+ (BOOL)resolveClassMethod:(SEL)sel
中进行动态添加方法class_addMethod()
,并返回YES
; - 对于实例方法,可在
- (BOOL)resolveInstanceMethod:(SEL)sel
中进行动态添加方法class_addMethod()
,并返回YES
; - 动态方法解析实现了后,会重新走消息发送流程,从方法缓存开始重新查找
- 对于类方法,可在
-
如果动态方法解析未实现(返回了
NO
),则进入消息转发流程- 判断方法
- (id)forwardingTargetForSelector:(SEL)aSelector
是否返回了转发目标,如果返回了,消息转发流程结束,否则进入下一步 - 判断方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
是否返回了方法签名,如果返回了进入下一步,否则抛出异常 - 实现
- (void)forwardInvocation:(NSInvocation *)anInvocation
进行消息转发操作
- 判断方法
# 实例方法动态解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) { # 要拦截的方法
# 要动态添加的方法的IMP
IMP imp = class_getMethodImplementation([self class], @selector(dynamicMethod));
# 最后一个参数const char *types: 参数、返回值编码
#”v@:”意思就是这已是一个void类型的方法,没有参数传入。
# “i@:”就是说这是一个int类型的方法,没有参数传入。
# ”i@:@”就是说这是一个int类型的方法,又一个参数传入。
class_addMethod(self, @selector(test), imp, "v@:");
return [super resolveInstanceMethod: sel];
}
return NO;
}
- (void)dynamicMethod {
NSLog(@"afasf");
}
# 类方法动态解析
+ (BOOL)resolveClassMethod:(SEL)sel {
}
# 消息转发目标
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(test)) {
return [Cat new]; # 返回对象不会向下执行
}
return [super forwardingTargetForSelector: aSelector]; # 继续向下进行
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
# 需要返回方法签名 否则抛出异常
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
# 进行自定义转发操作
}
思考 super
关键字
# Father类继承于NSObject Son类继承于Father
# Son类初始化方法如下, 如果实例化Son对象,下面会如何打印❓
- (instancetype)init {
if (self = [super init]) {
NSLog(@"[self class] = %@", [self class]);
NSLog(@"[self superclass] = %@", [self superclass]);
NSLog(@"[super class] = %@", [super class]);
NSLog(@"[super superclass] = %@", [super superclass]);
}
return self;
}
答案:
[self class] = Son
[self superclass] = Father
[super class] = Son
[super superclass] = Father
解析:
[self class]
编译阶段会变成:
* self是receiver 消息接受者
objc_msgSend(self, @selector(class));
[super class]
编译阶段会变成:
* 第一个参数 结构体中的self是receiver 消息接受者
* 第一个参数 结构体中的 [Father Class]是表示从父类Father开始查找方法
objc_msgSendSuper( {self, [Father Class]} , @selector(class));
[self class]
会从当前实例对象的类对象开始搜索class
方法,一直搜索到根类NSObject类
[super class]
会从当前实例对象的类对象的父类对象开始搜索class
方法,一直搜索到根类NSObject类,与[self class]
的不同只是开始搜索位置不同
class
方法的内部实现:
- (Class)class{
return object_getClass(self);
}
所以最终返回的结果取决于self
,而self
是消息调用者又叫消息接受者,即receiver
superClass
的内部实现:
- (Class)superclass {
return class_getSuperclass(object_getClass(self));
}
获取self的super 都是Father