OC底层原理11-objc_msgSend源码分析(方法查找快流
2020-09-21 本文已影响0人
Gomu_iOS
我们在 OC底层原理10-cache_t分析(插入流程) 一文中探索了cache
的插入流程,那cache是谁来读取的呢?又是怎么读取的呢?这就是本次研究的重心:objc_msgSend
方法查找流程之快流程cache
的读取
一、准备工作
1.1、objc4可编译源码,可直接跳到文章最后,下载调试好的源码
1.2、clang
编译.cpp
文件,OC底层原理04-对象的本质 一文中有clang
命令简介
1.3、新建一个macOS-Command Line Tool
项目,.main实现如下
#import <Foundation/Foundation.h>
@interface GomuTeacher : NSObject
- (void)sayHello;
@end
@implementation GomuTeacher
- (void)sayHello{
NSLog(@"%s",__func__);
}
@end
@interface GomuPerson : GomuTeacher
- (void)sayHello;
- (void)sayNB;
@end
@implementation GomuPerson
- (void)sayNB{
NSLog(@"%s",__func__);
}
- (void)sayHello{
NSLog(@"%s",__func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
二、方法的本质
2.1、通过clang
将.m
编译成.cpp
文件,查看main函数中方法调用的实现如下
//: .main object代码
GomuPerson *person = [GomuPerson alloc];
[person sayNB];
[person sayHello];
//: .cpp 编译后的c++代码
GomuPerson *person = ((GomuPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("GomuPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
结论:
方法的本质
就是objc_msgSend消息发送
2.2、调用runtime
api,验证上述结论
GomuPerson *person = [GomuPerson alloc];
//: 用实例对象调用方法
[person sayNB];
[person sayHello];
//: 用`objc_msgSend`方式调用方法
objc_msgSend(person, sel_registerName("sayNB"));
objc_msgSend(person, sel_registerName("sayHello"));
//: 打印
-[GomuPerson sayNB]
-[GomuPerson sayHello]
-[GomuPerson sayNB]
-[GomuPerson sayHello]
//: 扩展,调用GomuPerson父类GomuTeacher中的sayHello,不实例化teacher
//: 构造参数一 objc_super
struct objc_super gomuJc_super;
gomuJc_super.receiver = person;
gomuJc_super.super_class = [GomuTeacher class];
//: 调用objc_msgSendSuper
objc_msgSendSuper(&gomuJc_super, sel_registerName("sayHello"));
//: 打印
-[GomuTeacher sayHello]
- 导入头文件
#import "objc/message.h"
- 修改配置将严厉的检查机制关掉,否则
objc_msgSend
的参数会报错,Build Settings
->Enable Strict Checking of objc_msgSend Call
->NO
-
sel_registerName
相当于上层的@selector
或者NSSelectorFromString
三、objc_msgSend快速查找流程(读取cache)分析
由于objc_msgSend
是由汇编写的,而我们研究的是arm64构架下的源码,所以直接进入objc4-781.2 源码中找到objc-msg-arm64.s
查看,.s
后缀代表汇编源码
3.1 找入口ENTRY _objc_msgSend
,源码如下
//: -- objc_msgSend 汇编入口
ENTRY _objc_msgSend
//: -- 无窗口
UNWIND _objc_msgSend, NoFrame
//: -- p0:objc_msgSend的第一个参数,即消息接受者
//: -- cmp: 比较
//: -- #0:nil
//: -- 判断p0是否为空
cmp p0, #0 // nil check and tagged pointer check
//: -- 支持taggedpointer(小对象类型)
#if SUPPORT_TAGGED_POINTERS
//: -- b.le: 执行标号,判断上面cmp的值是小于等于LNilOrTagged
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
//: -- b.eq: 执行标号,判断上面cmp的值等于LReturnZero
//: -- p0为空,返回nil
b.eq LReturnZero
#endif
//: -- p0不为空
//: -- p13 = x0栈内存中的值,即把isa赋值给p13
ldr p13, [x0] // p13 = isa
//: -- 通过isa & mask,然后得到class,这个后面单独分析
GetClassFromIsa_p16 p13 // p16 = class
//: -- #define LGetIsaDone 7,可以通过LGetIsaDone跳到这里,执行下面语句
LGetIsaDone:
// calls imp or objc_msgSend_uncached
//: -- 如果isa存在,调用CacheLookup,开始cache查找流程(快速查找流程sel->imp)
//: -- 找到就返回imp,没找到就返回objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
//: -- LNilOrTagged条件判断逻辑
LNilOrTagged:
b.eq LReturnZero // nil check
// tagged
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
adrp x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGE
add x10, x10, _OBJC_CLASS_$___NSUnrecognizedTaggedPointer@PAGEOFF
cmp x10, x16
b.ne LGetIsaDone
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
objc_msgSend
第一个流程图:
3.2 GetClassFromIsa_p16
源码分析
//: -- .macro 汇编宏定义
.macro GetClassFromIsa_p16 /* src */
//: -- __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
#if SUPPORT_INDEXED_ISA
//: -- 把传入的值src赋值给p16,p16 = src
mov p16, $0 // optimistically set dst = src
//: -- 判断如果不是非指针isa,则跳转到1,直接结束
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
//: -- 如果是非指针isa,则走这里
//: -- adrp: 通过基地址 + 偏移 获得一个字符串(全局变量),后面看不懂
adrp x10, _objc_indexed_classes@PAGE
//: -- add: 相加,后面看不懂
add: x10, x10, _objc_indexed_classes@PAGEOFF
//: -- ubfx: 无符号位段提取
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
//: -- ldr: 将p16后面的值赋值给p16
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
//: -- 如果是64位,我们当前研究的环境arm64 & LP64 会走这里
#elif __LP64__
//: -- 传入的值src & ISA_MASK(isa的面具)
and p16, $0, #ISA_MASK
#else
//: -- 如果是32位,则这就把src赋值给mov
// 32-bit raw isa
mov p16, $0
#endif
//: -- 宏定义结束
.endmacro
3.3 CacheLookup
源码分析
//: -- 定义CacheLookup宏
.macro CacheLookup
//: -- 从$1开始查询,objc_msgSend第二个参数sel
LLookupStart$1:
// p1 = SEL, p16 = isa
//: -- #define CACHE (2 * __SIZEOF_POINTER__),CACHE = 2*8 = 16
//: -- 从isa地址(cls首地址)开始平移16位,取出cache,存入p11中
//: -- isa 第一位站8字节,superclass占8字节,平移16位就是cache
//: -- arm64中_maskAndBuckets存在cache第一个位置
//: -- p11 = _maskAndBuckets,前16位是mask,后48位是buckets
ldr p11, [x16, #CACHE] // p11 = mask|buckets
//: -- 64位真机
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//: -- p11 & #0x0000ffffffffffff(前16位为0后48位为1),相当于把前16位抹零,取到buckets,存入寄存器p10中
and p10, p11, #0x0000ffffffffffff // p10 = buckets
//: -- LSR #48:逻辑右移48位,则拿到mask,存入寄存器p11中
//: -- p1(sel) & p11(mask),得到sel-imp的下标index
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
//: -- 非64位真机
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
//: -- p12: 下标index,p10: buckets数组首地址,PTRSHIFT:3
//: -- (_cmd & mask),取余,如果mask = 3 相当于取(0,1,2)
//: -- p12, LSL #(1+PTRSHIFT):index逻辑左移4位,相当于index*16
//: -- p12 = buckets + index*16,通过位移,拿到当前buckets中存在index位置的bucket
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
//: -- ldp:取栈内存中的值
//: -- p17 = imp,p9 = sel
ldp p17, p9, [x12] // {imp, sel} = *bucket
//: -- p9:当前位置bucket中存的sel
//: -- 传入的sel
//: -- 比较当前位置bucket中存的sel和传入的sel
1: cmp p9, p1 // if (bucket->sel != _cmd)
//: -- 如果不相等就跳转到2
b.ne 2f // scan more
//: -- 如果想等,则找到传入sel存在缓存中的bucket,返回imp,缓存命中
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
//: -- 如果一直找不到,因为这里是normarl,跳转至__objc_msgSend_uncached
CheckMiss $0 // miss if bucket->sel == 0
//: -- p12:当前下标中的bucket
//: -- p10:buckets首地址,存的buckets第一个下元素
//: -- 判断当前bucket是否等于buckets的第一个元素
cmp p12, p10 // wrap if bucket == buckets
//: -- 如果相等,跳转到3
b.eq 3f
//: -- 如果不相等,则向前遍历
//: -- 从x12(即p12 buckets首地址),实际需要平移的内存大小BUCKET_SIZE,得到得到第二个bucket元素,imp-sel分别存入p17-p9,即向前查找
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
//: -- 跳转到第一步,继续对比,循环遍历
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
//: -- 真机64位
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//: -- p11:_maskAndBuckets
//: -- p12:前下标中的bucket
//: -- p11,LSR #(48 - (1+PTRSHIFT)),p11逻辑右移44位,相当于mask左移4位
//: -- 主动设置到最后一个元素 (有疑问)
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
// p12 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p12, p12, p11, LSL #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
//: -- ldp:取栈内存中的值
//: -- p17 = imp,p9 = sel
//: -- 再查找一遍缓存
ldp p17, p9, [x12] // {imp, sel} = *bucket
//: -- 比较当前位置bucket中存的sel和传入的sel
1: cmp p9, p1 // if (bucket->sel != _cmd)
//: -- 如果不相等就跳转到2
b.ne 2f // scan more
//: -- 如果想等,则找到传入sel存在缓存中的bucket,返回imp,缓存命中
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
//: -- 如果一直找不到,因为这里是normarl,跳转至__objc_msgSend_uncached
CheckMiss $0 // miss if bucket->sel == 0
//: -- 判断当前bucket是否等于buckets的第一个元素
cmp p12, p10 // wrap if bucket == buckets
//: -- 如果相等,跳转到3
b.eq 3f
//: -- 如果不相等,则向前遍历
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
//: -- 跳转到第一步,继续对比,循环遍历
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
//: -- 跳转至JumpMiss 因为是normal ,跳转至__objc_msgSend_uncached
JumpMiss $0
.endmacro
objc_msgSend
快速查询流程图
四、拓展知识-运行时
4.1 定义
运行时:是装载在内存,提供运行时功能(运行时的功能依赖runtime)
runtime:是一套由C、C++、汇编一起写成的api,给OC提供运行时
4.2 编译时
与运行时
相对应的是编译时
编译时:是源代码翻译成机器能识别的代码的过程,主要是对语言进行最基本的检查报错,即词法分析、语法分析等,是一个静态的阶段
4.3 运行时与编译时的区别
- 编译时就报错的就是编译时,比如语法、词法自动纠错
- 运行之后才报错的就是运行时,比如,你写的bug~
比如:申明一个方法sayHello
,但不实现它,直接调用[p sayHello]
,我们直接command+B不会报错,这就是编译时,但是如果我们运行command+R就会崩溃,这就是运行时
4.4 Runtime的三种调用方式
- 通过
OC代码
:[p sayHello] - 通过
NSObject方法
:isKindOfClass、isMeberOfClass - 通过
Runtime API
:class_getInstanceSize,objc_msgSend
其三种实现方法与编译层和底层的关系如图所示
image.png
compiler
就是编译器,即LLVM
,例如OC
的alloc
对应底层的objc_alloc
,runtime system libarary
就是底层系统库