备用知识文章
iOS看源码:cache_t方法缓存
1、一般使用runtime的几中方式
直接在 OC 层进行交互:比如 @selector;
NSObjCRuntime 的:比如NSSelectorFromString 方法;
Runtime API: 比如sel_registerName。
2、消息发送 clang之后的源文件
clang -rewrite-objc main.m -o main.cpp
使用clang编译一下main文件输出.cpp文件
int main(int argc, const char * argv[]) {
MyClass *myClass = [[MyClass alloc]init];
[myClass say];
return 0;
}
int main(int argc, const char * argv[]) {
MyClass *myClass = ((MyClass *(*)(id, SEL))(void *)objc_msgSend)((id)((MyClass *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyClass"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)myClass, sel_registerName("say"));
return 0;
}
可以看到[myClass say];
这段代码最后被编译为 id objc_msgSend(id self, SEL _cmd, ...);
这种形式
那么 关于objc_msgSend底层实现的源码 在官方给出的文档中(arm64架构) 是以.s文件的汇编源码给出的。
3、objc_msgSend 汇编源码
用汇编写:快 动态性(不确定)
arm-64环境下的objc_msgsend
汇编源码
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 //判断消息接受者是否存在 p0与0做比较 p0存放的是消息接受者
#if SUPPORT_TAGGED_POINTERS //对消息是否为空的处理
b.le LNilOrTagged
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // x0存放的是isa指针 放进p13 p13 = isa
GetClassFromIsa_p16 p13 //取出isa中的class地址
LGetIsaDone:
CacheLookup NORMAL, _objc_msgSend //从缓存中获取imp
//...
LLookup_GetIsaDone:
// returns imp
CacheLookup LOOKUP, _objc_msgLookup
.macro GetClassFromIsa_p16
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, $0 // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
// 64-bit 环境下 将isa与上mask 获得class地址 放在p16中
and p16, $0, #ISA_MASK
#else
// 32-bit raw isa
mov p16, $0
#endif
上面是消息发送的流程 按照汇编的流程来看 进行参数检查之后进入缓存查找流程
#define CLASS __SIZEOF_POINTER__ //8
#define CACHE (2 * __SIZEOF_POINTER__) //2*8=16
#define PTRSIZE 8
#define PTRSHIFT 3 // 1<sel和_cmd是否相等 相等说明找到了对应的bucket 否则未找到 继续循环
1: cmp p9, p1
//未找到 继续找
b.ne 2f
//找到了对应的bucket 则返回相应的结果(call or return imp)
CacheHit $0
2: // not hit: p12 = not-hit bucket 上面没找到 这里继续找bucket
CheckMiss $0 // miss if bucket->sel == 0 没有找到缓存 走下一步
//是p12正在查找的bucket p10是buckets首地址 相等说明p12是第一个元素
cmp p12, p10 // wrap if bucket == buckets
//上面结果相同 去3f 不同 跳过下面继续走
b.eq 3f
// {imp, sel} = *--bucket 循环查找 每次指针迁移一位
ldp p17, p9, [x12, #-BUCKET_SIZE]!
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask 真机下 mask存放在高16位
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//p11 = mask|buckets (p11 >> 44) = mask
// p12 = buckets + (mask << 1+PTRSHIFT) 作用是把指针指向数组中最后一个bucket的位置
add p12, p12, p11, LSR #(48 - (1+PTRSHIFT))
#else
//...
#endif
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
LLookupEnd$1:
LLookupRecover$1:
3: // double wrap
JumpMiss $0
.endmacro
当查找不到缓存时 就会进入__objc_msgSend_uncached
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup //这里是重点
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
这一段的重点在于MethodTableLookup
.macro MethodTableLookup
//....
bl _lookUpImpOrForward
//.…
.endmacro
_lookUpImpOrForward
这个方法应该非常熟悉
这里 方法的查找就进入了慢速的查找流程
下一篇再继续