【iOS面试#3】方法与消息的简单分析
1. 消息的本质
调用objc_msgSend
,第一个参数是消息接收者,第二个是方法编号
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
objc_msgSend(person,sel_registerName("NB"));
2. 方法查找
1). 开始查找
-
① 开始
objc_msgSend
-
② 判断
消息接收者
是否为空,为空直接返回 -
③ 判断
tagged_pointers
(之后会讲到) -
④ 取得对象中的
isa
-
⑤ 根据
isa
进行mask
地址偏移得到对应的上级对象
(类、元类) -
⑥ 开始在缓存中查找
imp
——开始了快速流程
2). 快速查找流程
objc_class
结构
struct objc_class : objc_object {
// Class ISA; //8 //继承了一个ISA
Class superclass; //8
cache_t cache;//16 // formerly cache pointer and vtable
class_data_bits_t bits;// 存放数据 // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const { //获取数据的方法
return bits.data();
}
}
cache_t
结构
struct cache_t {
private:
//显示原子性,保证增删改查线程安全
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //8
union {
struct {
explicit_atomic<mask_t> _maybeMask; //4
#if __LP64__
uint16_t _flags; //2
#endif
uint16_t _occupied; //2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
}
-
① 通过
receiver
消息接收者首地址平移16字节
(isa
8字节 +superClass
8字节)获得cache
-
②从
cache
中分别取出buckets
和mask
,并由mask
根据哈希算法计算出哈希下标- 通过
cache
和掩码(即0x0000ffffffffffff)
的&
运算,将高16位mask抹零
,得到buckets
指针地址 - 将
cache
右移48
位,得到mask
- 将
objc_msgSend
的第二个参数_cmd & msak
,通过哈希算法
,得到需要查找存储sel-imp
的bucket
下标index = _cmd & mask
,存储sel-imp
时,也是通过同样哈希算法计算哈希下标进行存储,所以读取
也需要通过同样的方式读取
,如下所示
- 通过
- ③ 根据所得的哈希下标
index
乘以单个bucket
占用的内存16字节(sel
占8字节,imp
占8字节)得到偏移地址, 和通过buckets
首地址加偏移地址,取出哈希下标index
对应的bucket
,通过获得的bucket
取出sel
和imp
- ④ 循环比对 ③ 中得到的
sel
与objc_msgSend
的第二个参数的_cmd
是否相等,相等则缓存命中,返回imp
,不等,则匹对buckets
的其他bucket
,仍然没找到,则跳转至__objc_msgSend_uncached
,进入 ==> 3). 慢速查找流程。
3). 慢速查找流程
在2). 快速查找流程 中进行查找没有找到,进行下面步骤:
-
① 判断cls
- 是否是
已知类
,如果不是,则报错
- 类是否
实现
,如果没有,则需要先实现,确定其父类链,此时实例化的目的是为了确定父类链、ro、以及rw等,方便后续数据的读取以及查找的循环 - 是否
初始化
,如果没有,则初始化
- 是否是
-
② 循环查找,按照
类继承链
或者元类继承链
的顺序查找-
当前
cls
的方法列表中使用二分查找算法
查找方法,如果找到,则进入cache写入流程
,并返回imp
,如果没有找到,则返回nil
-
当前
cls
被赋值为父类
,如果父类等于nil
,则imp = 消息转发
,并终止递归,进入③ -
如果
父类链
中存在循环
,则报错,终止循环
-
父类缓存
中查找方法- 如果
未找到
,则直接返回nil
,继续循环查找
- 如果
找到
,则直接返回imp
,执行cache写入流程
- 如果
-
-
③
判断
是否执行过动态方法解析
- 如果
没有
,执行动态方法解析
- 如果
执行过一次
动态方法解析,则走到消息转发流程
- 如果
总结
- 对于
对象方法(即实例方法)
,即在类中查找
,其慢速查找的父类链
是:类--父类--根类--nil
- 对于
类方法
,即在元类中查找
,其慢速查找的父类链
是:元类--根元类--根类--nil
- 如果
快速查找、慢速查找
也没有找到方法实现,则尝试动态方法决议
- 如果
动态方法决议
仍然没有找到,则进行消息转发
4). 动态方法解析
resolveClassMethod
// 未实现的方法为A
// 替换实现的方法为B
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector("未实现A")) {
// 获取已实现的方法B的IMP
IMP imp = class_getMethodImplementation(self, @selector("已实现B"));
// 获取实例
Method m = class_getInstanceMethod(self, @selector("已实现B"));
// 获取方法签名
const char *type = method_getTypeEncoding(m);
// 返回实现
return class_addMethod(self, sel, imp, type);
}
// 未处理
return [super resolveInstanceMethod:sel];
}
- (void)B {
// B的实现
}
resolveClassMethod
+ (BOOL)resolveClassMethod:(SEL)sel{
if (sel == @selector("未实现A")) {
IMP imp = class_getMethodImplementation(objc_getMetaClass("类名"), @selector("已实现B"));
Method m = class_getInstanceMethod(objc_getMetaClass("类名"), @selector("已实现B"));
const char *type = method_getTypeEncoding(m);
return class_addMethod(objc_getMetaClass("类名"), sel, imp, type);
}
// 未处理
return [super resolveClassMethod:sel];
}
+ (void)B {
// B的实现
}
5). 消息转发
a). 快速转发
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == "未实现的方法") {
return ["能解决问题的类" alloc]; //比如上传bug
}
return [super forwardingTargetForSelector:aSelector];
}
b). 慢速转发
慢速转发流程
就是先methodSignatureForSelector
提供一个方法签名,然后forwardInvocation
通过对NSInvocation
来实现消息的转发
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSMethodSignature signatureWithObjCTypes:"v@:"]
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
anInvocation.target = ["解决问题的类" alloc];
[anInvocation invoke];
}
总结:
转载自:https://juejin.cn/post/7055563461329158174