首先来温习一下缓存
运行时:顾名思义就是代码跑起来,被装载到内存中去了。markdown
Runtime有两个版本:函数
早期版本用于Object-C 1.0 32位的Mac OS X的平台上,现行版本iPhone程序和Mac OS X10.5及之后系统的64位程序。oop
Runtime层级:ui
来一段举例代码spa
#import <objc/message.h>
@interface CFFahter : NSObject
- (void)sayHello;
- (void)sayHow;
@end
@implementation CFFahter
- (void)sayHello{
NSLog(@"sayHello");
}
- (void)sayHow{
NSLog(@"sayHow");
}
@end
@interface CFSon : CFFahter
- (void)sayHello;
- (void)sayHow;
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
CFSon *son = [[CFSon alloc]init];
objc_msgSend(son,sel_registerName("sayHow"));
[son sayHow];
/*
CFSon *son = ((CFSon *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CFSon"), sel_registerName("alloc")); ((void (*)(id, SEL))(void *)objc_msgSend)((id)son, sel_registerName("sayHow")); ((void (*)(id, SEL))(void *)objc_msgSend)((id)son, sel_registerName("sayHello")); */
}
return NSApplicationMain(argc, argv);
}
复制代码
编译后底层代码实现rest
CFSon *son = ((CFSon *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("CFSon"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)son, sel_registerName("sayHow")); ((void (*)(id, SEL))(void *)objc_msgSend)((id)son, sel_registerName("sayHello"));
复制代码
咱们发现,在上层的@selector()对应底层sel_registerName,还能够等价于NSSeletorFromString(),code
接下来咱们试着换一种方式来调用sayHow方法orm
objc_msgSend(son,sel_registerName(@"sayHow"));
复制代码
此刻发现会报错多了一个参数,缘由是当前objc_msgSend 函数默认参数只有一个,只要把其检查机制关掉便可对象
打印结果:
接下来再添加一个继承于CFFahter的子类CFSon类,为父类发送消息
@interface CFSon : CFFahter
- (void)sayHello;
- (void)sayHow;
@end
@implementation CFSon
- (void)sayHello{
NSLog(@"sayHow");
}
- (void)sayHow{
NSLog(@"sayHow");
}
@end
复制代码
此时须要另外一个向父类发送消息的API
//objc_msgSendSuper(<#struct objc_super * _Nonnull super#>, <#SEL _Nonnull op, ...#>)
struct objc_super cfsuper;
cfsuper.receiver = son;
cfsuper.super_class = [CFFahter class];
objc_msgSendSuper(&cfsuper, sel_registerName("sayHello"));
复制代码
sayHow方法一样能打印
在objc_msgSendSuper方法中,第一个参数是一个objc_super的结构体,结构体里有一个接收消息的receiver和一个当前父类super_class两个变量。
消息接受者流程:
对象 - ISA - 方法(类) - cache_t - methodlist
#endif
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone://获取isa完毕后
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend //开始从缓存里面imp流程
//GetClassFromIsa_p16 宏:
.macro GetClassFromIsa_p16 /* src */#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:
复制代码
CacheLookup内部实现
.macro CacheLookup
//
// Restart protocol:
//
// As soon as we're past the LLookupStart$1 label we may have loaded
// an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd$1,
// then our PC will be reset to LLookupRecover$1 which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
LLookupStart$1:
// p1 = SEL, p16 = isa
ldr p11, [x16, #CACHE]//CACHE宏定义: (2* _ _SIZEOF_POINTER)
// p11 = mask|buckets
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#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
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)查询sel是否相同
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
3: // wrap: p12 = first bucket, w11 = mask //mask = copu - 1 = 4 - 1 = 3,
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
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, 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
复制代码
大概过程咱们可用经过一段伪代码来简单梳理一下
//获取当前的对象
id son = 0x1000
//获取isa
isa_t isa = 0x000000
//isa -> class(类) -> cache
cache_t cache - isa + 16字节
//mask/bucjets 在arm64环境下
buckets = cache & 0x000ffffffffff
//获取mask
mask = cache LSR #48
//下标 = mask & sel
index = mask &s1
//从buckets遍历得到单个bucket
bucket = buckets + index * 16 //(sel imp = 16)
int count = 0;
do {
if(count == 2){ goto CheckMiss,//出口
}
//接下来循环
if(bucket.sel == _cmd){
//判断bucket里面的sel 是否匹配_cmd,直接返回imp
if(bucket == buckets){
//第二层判断,bucket = 第一个元素
//bucket人为设定的最后一个元素
bucket = buckets + mask * 16
count ++;
}
//向前查找的顺序进行缓存的查找
bucket --;
imp = bucket.imp;
sel = bucket.sel;
}while (bucket.sel != _cmd)
return imp
CheckMiss:
CheckMiss(normal)
复制代码
objc_msgSend流程分析图