1. 回顧
在iOS底層探索之類的結(jié)構(gòu)(上) 中介紹了類中的isa
,在iOS底層探索之類的結(jié)構(gòu)(中)介紹了類中的bits
拄养,還有一個(gè)cache
沒有探索和分析,這次主要是分析cache
屬性岂昭。
2. cache 結(jié)構(gòu)
我們的目的是探索cache
雾家,首先得先去了解它的結(jié)構(gòu)艘狭,然后再具體分析填抬。
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
...此處省略代碼...
}
從類的結(jié)構(gòu)中,可以看到cache
是cache_t
類型的惶洲,那么我們?nèi)?code>cache_t里面看看衬横。
2.1 cache_t
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
union {
struct {
explicit_atomic<mask_t> _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache;
};
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
// _bucketsAndMaybeMask is a buckets_t pointer
// _maybeMask is the buckets mask
static constexpr uintptr_t bucketsMask = ~0ul;
static_assert(!CONFIG_USE_PREOPT_CACHES, "preoptimized caches not supported");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
static constexpr uintptr_t maskShift = 48;
static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << maskShift) - 1;
static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#if CONFIG_USE_PREOPT_CACHES
static constexpr uintptr_t preoptBucketsMarker = 1ul;
static constexpr uintptr_t preoptBucketsMask = bucketsMask & ~preoptBucketsMarker;
#endif
...此處省略代碼...
}
從底層源碼我們很容易看出裹粤,
cache_t
的結(jié)構(gòu),我們也可以代碼測(cè)試lldb
查看
從控制臺(tái)輸出可以看到結(jié)構(gòu)是一模摸一樣樣冕香。查看
cache_t
的源碼蛹尝,我們還發(fā)現(xiàn)底層分成了3個(gè)架構(gòu)來處理,其中真機(jī)的架構(gòu)中mask
和bucket
是寫在一起悉尾,目的是為了優(yōu)化突那,可以通過各自的掩碼來獲取相應(yīng)的數(shù)據(jù)。
-
CACHE_MASK_STORAGE_OUTLINED
: 表示運(yùn)行的環(huán)境是模擬器
或者macOS
系統(tǒng) -
CACHE_MASK_STORAGE_HIGH_16
: 表示運(yùn)行環(huán)境是64
位的真機(jī) -
CACHE_MASK_STORAGE_LOW_4
:表示運(yùn)行環(huán)境是非64位
的真機(jī)
在cache_t
的結(jié)構(gòu)里面還發(fā)現(xiàn)了如下代碼
static bucket_t *emptyBuckets();
static bucket_t *allocateBuckets(mask_t newCapacity);
static bucket_t *emptyBucketsForCapacity(mask_t capacity, bool allocate = true);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void bad_cache(id receiver, SEL sel) __attribute__((noreturn, cold));
從這些代碼中可以构眯,知道是對(duì)bucket_t
的進(jìn)行了操作愕难,那么這個(gè)bucket_t
是個(gè)什么重要角色呢?
2.2 bucket_t
以下是bucket_t
核心代碼
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
explicit_atomic<uintptr_t> _imp;
explicit_atomic<SEL> _sel;
#else
explicit_atomic<SEL> _sel;
explicit_atomic<uintptr_t> _imp;
#endif
// Compute the ptrauth signing modifier from &_imp, newSel, and cls.
uintptr_t modifierForSEL(bucket_t *base, SEL newSel, Class cls) const {
return (uintptr_t)base ^ (uintptr_t)newSel ^ (uintptr_t)cls;
}
...此處省略代碼...
}
從上面??的bucket_t
的結(jié)構(gòu)體源碼可以看出,bucket_t
里面存儲(chǔ)的是SEL
和 IMP
猫缭,同樣分為兩個(gè)版本葱弟,真機(jī) 和 非真機(jī),區(qū)別在于SEL
和IMP
的順序不一致猜丹。
到此大概知道了cache
是和方法有關(guān)的了芝加,這家伙就是方法緩存嘛??。
由此可以畫出一個(gè)簡(jiǎn)單的結(jié)構(gòu)圖射窒,如下
那么到底是不是方法緩存呢藏杖?又是如何進(jìn)行方法緩存的呢?我接著往下探索分析
2.2.1 buckets()
從源碼中發(fā)現(xiàn)脉顿,有個(gè)buckets()
方法可以獲取bucket_t
(lldb) p $2.buckets()
(bucket_t *) $3 = 0x00000001003623c0
(lldb) p *$3
(bucket_t) $4 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb)
好尷尬膀螋铩!什么都么有鞍薄来吩!_sel
值為nil
?不是方法緩存嗎蔽莱?方法哪里去了暗芙!
這是因?yàn)槲覀兌紱]有調(diào)用方法碾褂,哪里來的緩存那兽间!那就調(diào)用一個(gè)方法再看看
(lldb) p [p sayHello]
2021-06-25 15:37:47.401935+0800 JPBuild[18788:5401308] -[JPPerson sayHello]
(lldb) p/x pClass
(Class) $5 = 0x0000000100008688 JPPerson
(lldb) p (cache_t*)0x0000000100008698
(cache_t *) $6 = 0x0000000100008698
(lldb) p *$6
(cache_t) $7 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4301295648
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 7
}
}
_flags = 32808
_occupied = 1
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0001802800000007
}
}
}
}
(lldb) p *$7.buckets()
(bucket_t) $8 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb)
2.2.2 sel()、imp()
什么鬼??正塌??恤溶?納尼乓诽?還是沒有啊咒程!但是我們發(fā)現(xiàn)了_maybeMask
鸠天、_flags
、_occupied
是有值的帐姻。于是繼續(xù)查看源碼稠集,發(fā)現(xiàn)了,bucket_t
里面的sel()
方法饥瓷,這不就打印方法名的鞍住!
(lldb) p $7.buckets()[1]
(bucket_t) $10 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb) p $7.buckets()
(bucket_t *) $11 = 0x0000000100609020
(lldb) p *$11
(bucket_t) $12 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb) p $12.sel()
(SEL) $13 = (null)
(lldb) p $7.buckets()[2]
(bucket_t) $14 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb) p $7.buckets()[3]
(bucket_t) $15 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 49128
}
}
}
(lldb) p $15.sel()
(SEL) $16 = "sayHello"
(lldb)
可以看到輸出了我們調(diào)用的方法"sayHello"
呢铆,IMP
也是可以輸出的晦鞋,使用下面這個(gè)方法
inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const {
uintptr_t imp = _imp.load(memory_order_relaxed);
if (!imp) return nil;
輸出結(jié)果如下
(lldb) p $15.imp(nil,pClass)
(IMP) $17 = 0x0000000100003960 (JPBuild`-[JPPerson sayHello])
(lldb)
3. 脫離源碼分析
在上面是在底層源碼里面查看結(jié)構(gòu),并且結(jié)合LLDB
調(diào)試來分析的,那么我們?nèi)绻创a調(diào)式不了呢悠垛?改怎么辦呢线定?那么接下來就通過,模仿源碼結(jié)構(gòu)确买,直接代碼分析斤讥。
3.1 小規(guī)模取樣,模仿源碼結(jié)構(gòu)
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct jp_bucket_t {
SEL _sel;
IMP _imp;
};
struct jp_cache_t {
struct jp_bucket_t *_bukets; // 8
mask_t _maybeMask; // 4
uint16_t _flags; // 2
uint16_t _occupied; // 2
};
struct jp_class_data_bits_t {
uintptr_t bits;
};
// cache class
struct jp_objc_class {
Class isa;//在源碼中湾趾,objc_class的ISA屬性是繼承自objc_object的周偎,
//但在我們將其拷貝過來時(shí),去掉了objc_class的繼承關(guān)系撑帖,
//需要將這個(gè)屬性明確蓉坎,否則打印的結(jié)果是有問題的
Class superclass;
struct jp_cache_t cache; // formerly cache pointer and vtable
struct jp_class_data_bits_t bits;
};
方法
@implementation LGPerson
- (void)say1{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say2{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say3{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say4{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say5{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say6{
NSLog(@"LGPerson say : %s",__func__);
}
- (void)say7{
NSLog(@"LGPerson say : %s",__func__);
}
+ (void)sayHappy{
NSLog(@"LGPerson say : %s",__func__);
}
@end
3.2 代碼測(cè)試
調(diào)用兩個(gè)方法,打印看看胡嘿,是否緩存了方法
調(diào)用了兩個(gè)方法蛉艾,都打印出來了,那么我們多調(diào)用幾個(gè)方法看看
從以上兩個(gè)測(cè)試打印的結(jié)果來看衷敌,
_occupied
和_maybeMask
的值有變化勿侯,方法調(diào)用的數(shù)量不同值會(huì)變大。那么_occupied
和_maybeMask
這兩個(gè)家伙又是什么呢缴罗?
請(qǐng)看下一篇博客分析
iOS底層探索之類的結(jié)構(gòu)—cache分析(下)
更多內(nèi)容持續(xù)更新
?? 請(qǐng)動(dòng)動(dòng)你的小手助琐,點(diǎn)個(gè)贊????
?? 喜歡的可以來一波,收藏+關(guān)注面氓,評(píng)論 + 轉(zhuǎn)發(fā)兵钮,以免你下次找不到我,哈哈????
??歡迎大家留言交流舌界,批評(píng)指正掘譬,互相學(xué)習(xí)??,提升自我??