【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字节(isa8字节 +superClass8字节)获得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