cache拓展补充
buckets是什么结构?为什么是3/4扩容?
使用上节课代码进行lldb调试 cache分析
- 查看
cache_t
源码,发现分成了3个架构的处理,其中真机的架构中mask
和bucket
是写在一起,目的是为了优化,可以通过各自的掩码来获取相应的数据
CACHE_MASK_STORAGE_OUTLINED
表示运行的环境 模拟器 或者 macOS
CACHE_MASK_STORAGE_HIGH_16
表示运行环境是 64位的真机
CACHE_MASK_STORAGE_LOW_4
表示运行环境是 非64位 的真机
![](https://img.haomeiwen.com/i1212147/22557fa547f3b156.png)
![](https://img.haomeiwen.com/i1212147/b14180fb2561a42c.png)
- 查看
insert
源码
主要分为以下几部分
计算出当前的缓存占用量
根据缓存占用量判断执行的操作
针对需要存储的bucket进行内部imp和sel赋值
- 第一步 根据
occupied
的值计算出当前的缓存占用量,当属性未赋值及无方法调用时,此时的occupied()为0,而newOccupied为1,源码如下
mask_t newOccupied = occupied() + 1;
- 第二步 根据缓存占用量判断执行的操作
- 如果是第一次创建,则默认
开辟4个
if (slowpath(isConstantEmptyCache())) { //小概率发生的 即当 occupied() = 0时,即创建缓存,创建属于小概率事件
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE; //初始化时,capacity = 4(1<<2 -- 100)
reallocate(oldCapacity, capacity, /* freeOld */false); //开辟空间
//到目前为止,if的流程的操作都是初始化创建
}
- 如果缓存占用量
小于等于3/4
,则不作任何处理
else if (fastpath(newOccupied + CACHE_END_MARKER <= capacity / 4 * 3)) {
// Cache is less than 3/4 full. Use it as-is.
}
- 如果缓存占用量
超过3/4
,则需要进行两倍扩容
以及重新开辟空间
else {//如果超出了3/4,则需要扩容(两倍扩容)
//扩容算法: 有cap时,扩容两倍,没有cap就初始化为4
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE; // 扩容两倍 2*4 = 8
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
// 走到这里表示 曾经有,但是已经满了,需要重新梳理
reallocate(oldCapacity, capacity, true);
// 内存 扩容完毕
}
- 第三步 针对需要存储的bucket进行内部imp和sel赋值
这部分主要是根据cache_hash
方法即哈希算法 ,计算sel-imp
存储的哈希下标,分为以下三种情况:
- 如果哈希下标的位置
未存储sel
,即该下标位置获取sel等于0,此时将sel-imp存储进去,并将occupied占用大小加1 - 如果当前哈希下标存储的sel
等于
即将插入的sel,则直接返回 - 如果当前哈希下标存储的sel
不等于
即将插入的sel,则重新经过cache_next方法 即哈希冲突算法,重新进行哈希计算,得到新的下标,再去对比进行存储
得出结论
-
_bucketsAndMaybeMask
存储的就是buckets()
的首地址 -
3/4扩容
空间利用率最高,哈希冲突概率最低
cache读取流程分析
objc4源码
中objc_cache.mm
文件描述了cache的读取流程,如下所示
* Cache readers (PC-checked by collecting_in_critical())
* objc_msgSend*
* cache_getImp
*
* Cache readers/writers (hold cacheUpdateLock during access; not PC-checked)
* cache_t::copyCacheNolock (caller must hold the lock)
* cache_t::eraseNolock (caller must hold the lock)
* cache_t::collectNolock (caller must hold the lock)
* cache_t::insert (acquires lock)
* cache_t::destroy (acquires lock)
从上面可以看出,cache_getImp
发起了对cache的读写调用流程,用于读取缓存中函数调用地址
。在这之前又调用了objc_msgSend
,这就引出了本节课探索的重点objc_msgSend
函数,这个函数的流程是怎么的呢?下面进行探索......
OC的编译时与运行时
编译时
Building
,编译时是源代码翻译成机器能识别的代码的过程
,主要是对语言进行最基本的检查报错
,即词法分析、语法分析等,是一个静态的阶段,这些都是LLVM来做的
运行时
Running
,运行时 是代码跑起来,被装载到内存中的过程,如果此时出错,则程序会崩溃,是一个动态阶段
Runtime有两个版本
Objective-C Runtime Programming Guide
一个Legacy版本
(早期版本)
- 早期版本对应的编程接口:
Objective-C 1.0
- 早期版本用于Objective-C 1.0, 32位的
Mac OS X
的平台上
一个Modern版本
(现行版本)
- 现行版本对应的编程接口:
Objective-C 2.0
- 现行版本:
iPhone程序
和Mac OS X v10.5
及以后的系统中的64位程序
runtime的使用方式
runtime使用有三种方式,其三种实现方法与编译层和底层的关系如图所示
- 通过OC层面方法调用,例如
[person sayNB]
- 通过NSObject调用相关的API,例如
isKindOfClass
- 通过Runtime API,例如
class_getInstanceSize
![](https://img.haomeiwen.com/i1212147/461941c99143aa35.png)
Compiler
就是我们了解的编译器
即LLVM
,例如OC的alloc
对应底层的objc_alloc
,runtime system libarary
就是底层库
objc_msgSend流程
创建LGPerson
类,其中sayHello
方法没有实现,在main
函数中调用。代码如下
@interface LGPerson : NSObject
- (void)sayHello;
- (void)sayNB;
@end
@implementation LGPerson
- (void)sayNB{
NSLog(@"666");
}
@end
//main.m中方法的调用
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [LGPerson alloc];
[person sayNB];
[person sayHello];
}
return 0;
}
使用clang
把main.m
编译main.cpp
文件,通过查看main函数中方法调用的实现如下所示
//clang编译后的底层实现
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), 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消息发送
下面验证objc_msgSend
方法来完成[person sayNB]
的调用,查看其打印是否是一致
- 直接调用
objc_msgSend
,需要导入头文件#import <objc/message.h>
- 需要将
target --> Build Setting -->搜索msg -- 将enable strict checking of obc_msgSend calls由YES 改为NO
,将严厉的检查机制关掉,否则objc_msgSend
的参数会报错
LGPerson *person = [LGPerson alloc];
objc_msgSend(person,sel_registerName("sayNB"));
[person sayNB];
// 控制台打印
2021-07-25 11:59:18.414202+0800 001-运行时感受[55836:6041045] 666
2021-07-25 11:59:18.416631+0800 001-运行时感受[55836:6041045] 666
得出结论:[person sayNB]
等价于objc_msgSend(person,sel_registerName("sayNB"))
接下来我们修改代码如下,让person
调用执行父类中的实现
@interface LGTeacher : NSObject
- (void)sayHello;
@end
@implementation LGTeacher
- (void)sayHello{
NSLog(@"666 %s",__func__);
}
@end
@interface LGPerson : LGTeacher
- (void)sayHello;
- (void)sayNB;
@end
@implementation LGPerson
- (void)sayNB{
NSLog(@"666");
}
@end
// main中的调用
LGPerson *person = [LGPerson alloc];
[person sayHello];
// 控制台打印
2021-07-25 12:14:01.223238+0800 001-运行时感受[55928:6051186] 666 -[LGTeacher sayHello]
// 使用clang把main.m编译main.cpp文件,并在main.cpp文件中全局搜索objc_msgSend,结果发现了objc_msgSendSuper
#define __OBJC_RW_DLLIMPORT extern
__OBJC_RW_DLLIMPORT void objc_msgSend(void);
__OBJC_RW_DLLIMPORT void objc_msgSendSuper(void);
__OBJC_RW_DLLIMPORT void objc_msgSend_stret(void);
__OBJC_RW_DLLIMPORT void objc_msgSendSuper_stret(void);
__OBJC_RW_DLLIMPORT void objc_msgSend_fpret(void);
__OBJC_RW_DLLIMPORT struct objc_class *objc_getClass(const char *);
__OBJC_RW_DLLIMPORT struct objc_class *class_getSuperclass(struct objc_class *);
__OBJC_RW_DLLIMPORT struct objc_class *objc_getMetaClass(const char *);
__OBJC_RW_DLLIMPORT void objc_exception_throw( struct objc_object *);
__OBJC_RW_DLLIMPORT int objc_sync_enter( struct objc_object *);
__OBJC_RW_DLLIMPORT int objc_sync_exit( struct objc_object *);
__OBJC_RW_DLLIMPORT Protocol *objc_getProtocol(const char *);
上面[person sayHello]
为什么会触发到objc_msgSendSuper
,objc_msgSendSuper
底层又是什么结构呢?
原因是person
中没有sayHello
方法,就会去父类中查找,这时就会触发到objc_msgSendSuper
。查看源码发现objc_msgSendSuper
方法中有两个参数(结构体,sel)
,其结构体类型是objc_super
定义的结构体对象,且需要指定receiver
和super_class
两个属性。
源码实现如下
- objc_msgSendSuper方法
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
- objc_super方法
#ifndef OBJC_SUPER
#define OBJC_SUPER
/// Specifies the superclass of an instance.
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
#if !defined(__cplusplus) && !__OBJC2__
/* For compatibility with old objc-runtime.h header */
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
/* super_class is the first class to search */
};
#endif
既然已经探索到objc_msgSendSuper源码
与objc_super结构体
,我们修改main.m
中代码如下
LGPerson *person = [LGPerson alloc];
[person sayHello];
struct objc_super kc_objc_super;
kc_objc_super.receiver = person;//消息的接收者还是person
kc_objc_super.super_class = LGTeacher.class;//告诉父类是谁
//消息的接受者还是自己 - 父类 - 请你直接找我的父亲
objc_msgSendSuper(&kc_objc_super,@selector(sayHello));
// 控制台打印
2021-07-25 14:54:32.642329+0800 001-运行时感受[56624:6133486] 666 -[LGTeacher sayHello]
2021-07-25 14:54:32.642828+0800 001-运行时感受[56624:6133486] 666 -[LGTeacher sayHello]
得出结论:发现不论是[person sayHello]
还是objc_msgSendSuper
都执行的是父类中sayHello
的实现,所以这里我们可以作一个猜测:方法调用,首先是在类中查找,如果类中没有找到,会到类的父类中查找
。
objc_msgSend 汇编分析
objc_msgSend
是消息发送源码的入口,是使用汇编实现的。而其他方法是使用C或者C++
实现的。objc_msgSend
汇编根据sel查找imp
的流程实现如下
//---- 消息发送 -- 汇编入口--objc_msgSend主要是拿到接收者的isa信息
ENTRY _objc_msgSend
//---- 无窗口
UNWIND _objc_msgSend, NoFrame
//---- p0 和空对比,即判断接收者是否存在,其中p0是objc_msgSend的第一个参数-消息接收者receiver
cmp p0, #0 // nil check and tagged pointer check
//---- le小于 --支持taggedpointer(小对象类型)的流程
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
//---- p0 等于 0 时,直接返回 空
b.eq LReturnZero
#endif
//---- p0即receiver 肯定存在的流程
//---- 根据对象拿出isa ,即从x0寄存器指向的地址 取出 isa,存入 p13寄存器
ldr p13, [x0] // p13 = isa
//---- 在64位架构下通过 p16 = isa(p13) & ISA_MASK,拿出shiftcls信息,得到class信息
GetClassFromIsa_p16 p13 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
//---- 如果有isa,走到CacheLookup 即缓存查找流程,也就是所谓的sel-imp快速查找流程
CacheLookup NORMAL, _objc_msgSend
#if SUPPORT_TAGGED_POINTERS
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
方法的第一个参数receiver
是否为空
- 如果支持tagged pointer,跳转至
LNilOrTagged
如果小对象为空,则直接返回空,即LReturnZero
如果小对象不为空,则处理小对象的isa
- 如果即不是小对象,
receiver
也不为空,有以下两步
从receiver中
取出isa存入p13寄存器,
通过GetClassFromIsa_p16
中,arm64架构下通过isa & ISA_MASK
获取shiftcls位域
的类信息,即class,GetClassFromIsa_p16的汇编实现如下
.macro GetClassFromIsa_p16 /* src */
//---- 此处用于watchOS
#if SUPPORT_INDEXED_ISA
// Indexed isa
//---- 将isa的值存入p16寄存器
mov p16, $0 // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa -- 判断是否是 nonapointer isa
// isa in p16 is indexed
//---- 将_objc_indexed_classes所在的页的基址 读入x10寄存器
adrp x10, _objc_indexed_classes@PAGE
//---- x10 = x10 + _objc_indexed_classes(page中的偏移量) --x10基址 根据 偏移量 进行 内存偏移
add x10, x10, _objc_indexed_classes@PAGEOFF
//---- 从p16的第ISA_INDEX_SHIFT位开始,提取 ISA_INDEX_BITS 位 到 p16寄存器,剩余的高位用0补充
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
//--用于64位系统
#elif __LP64__
// 64-bit packed isa
//---- p16 = class = isa & ISA_MASK(位运算 & 即获取isa中的shiftcls信息)
and p16, $0, #ISA_MASK
#else
// 32-bit raw isa ---- 用于32位系统
mov p16, $0
#endif
.endmacro
-
获取isa
完毕,进入慢速查找流程CacheLookup NORMAL
汇编整体流程图如下
![](https://img.haomeiwen.com/i1212147/ac49876d8a05f157.png)
网友评论