文章開頭我再次貼出objc 類的源碼:
struct objc_class : objc_object {
//省略開頭代碼
// 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
//下面省略了大量代碼
}
我們前面學(xué)習(xí)的時(shí)候就發(fā)現(xiàn)了兽赁,我們的objc_class
類里面主要是四個(gè)成員:ISA
、superclass
耳璧、cache_t
治筒、class_data_bits
。我們也探索了其他三個(gè)赡艰,今天就來(lái)淺顯地看看我們跳過(guò)的這個(gè)cache_t
到底是個(gè)什么東西售淡。
前面我們獲取class_data_bits
的時(shí)候是利用內(nèi)存平移的方式得到的。那我們用同樣的方法來(lái)獲取cache_t
慷垮,因?yàn)?code>class_data_bits大小為16
揖闸,則我們平移的時(shí)候?qū)Ρ戎暗钠揭?code>32字節(jié)只需要平移16
字節(jié)了。
objc 源碼LLDB打印調(diào)試
我們直接在objc 源碼里創(chuàng)建一個(gè)ZYPerson
類料身,來(lái)利用lldb
打犹乐健(同之前的方法在NSLog
后一行打一個(gè)斷點(diǎn))上代碼:
#import <Foundation/Foundation.h>
#import "ZYPerson.h"
#import "ZYIoser.h"
//cache_t
// class_data_bits_t
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZYPerson *p = [ZYPerson alloc];
Class pClass = [ZYPerson class];
NSLog(@"%@",pClass);
}
return 0;
}
lldb) x/4gx pClass
0x1000086a0: 0x00000001000086c8 0x000000010036a140
0x1000086b0: 0x00000001003623a0 0x0000801800000000
(lldb) p/x 0x1000086a0+0x10
(long) $1 = 0x00000001000086b0
(lldb) p (cache_t *)0x00000001000086b0
(cache_t *) $2 = 0x00000001000086b0
(lldb) p *$2
(cache_t) $3 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4298515360
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 0
}
}
_flags = 32792
_occupied = 0
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0000801800000000
}
}
}
}
發(fā)現(xiàn)其中結(jié)構(gòu)是一個(gè)_bucketsAndMaybeMask
和一個(gè)聯(lián)合體
,聯(lián)合體中有
_maybeMask
芹血、_flags
贮泞、_occupied
楞慈、_originalPreoptCache
。如下:
struct cache_t {
private:
explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
union {
struct {
explicit_atomic<mask_t> _maybeMask; // 4
#if __LP64__
uint16_t _flags; // 2
#endif
uint16_t _occupied; // 2
};
explicit_atomic<preopt_cache_t *> _originalPreoptCache; // 8
};
//省略下面大量代碼
既然這個(gè)cache_t
是緩存啃擦,那到底緩存的是什么呢囊蓝?是屬性呢?還是方法呢令蛉?簡(jiǎn)單分析如果是緩存屬性那肯定有IMP
和 SEL
聚霜。下面我們就看看到底緩存了什么。那么我們到底該查看那個(gè)部分呢珠叔?是_bucketsAndMaybeMask
還是_originalPreoptCache
還是其他
呢蝎宇?我們回到源碼去查看下它提供的功能方法
,既然是緩存那肯定是針對(duì)某些東西有增刪改查
的操作祷安。
我們跟蹤cache_t
方法源碼往下看:
我們可以看到上圖紅框標(biāo)出的地方都是在對(duì)bucket_t
姥芥、Occupied
、mask
進(jìn)行一系列操作 并且在后面有一個(gè)insert
方法汇鞭。所以我們可以初步判斷他的重心在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
//省略了下面大量代碼。
我們可以理解為bucket就是一個(gè)桶子虱咧,里面裝的主要是imp和sel.
那么我們理解的數(shù)據(jù)結(jié)構(gòu)就是如下圖:
接下來(lái)我們利用lldb來(lái)驗(yàn)證下 方法的存儲(chǔ)是否是我們理解的這樣熊榛。我們挨個(gè)把看到的取一遍如下:
(explicit_atomic<unsigned long>) $4 = {
std::__1::atomic<unsigned long> = {
Value = 4298515360
}
}
(lldb) p $4.Value
error: <user expression 5>:1:4: no member named 'Value' in 'explicit_atomic<unsigned long>'
$4.Value
~~ ^
(lldb) p $3._maybeMask
(explicit_atomic<unsigned int>) $5 = {
std::__1::atomic<unsigned int> = {
Value = 0
}
}
(lldb) p $5.Value
error: <user expression 7>:1:4: no member named 'Value' in 'explicit_atomic<unsigned int>'
$5.Value
~~ ^
(lldb) p $3._originalPreoptCache
(explicit_atomic<preopt_cache_t *>) $6 = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0000801800000000
}
}
(lldb) p $6.Value
error: <user expression 9>:1:4: no member named 'Value' in 'explicit_atomic<preopt_cache_t *>'
$6.Value
~~ ^
(lldb)
我們發(fā)現(xiàn)里面的Value根本取不出來(lái),這個(gè)時(shí)候我們只能返回源碼去找找看是否有類似ro()
腕巡、data()
之類的方法.
如上圖我們?cè)?code>cache_t方法里找到一個(gè)buckets()
的方法。我們來(lái)嘗試一下血筑。
(lldb) p $3.buckets()
(bucket_t *) $7 = 0x00000001003623a0
(lldb) p *$7
(bucket_t) $8 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
(lldb)
接續(xù)上面的lldb調(diào)試打印buckets
發(fā)現(xiàn)什么都沒有.但是我們可以知道這樣取可以取绘沉。并且發(fā)現(xiàn)我們?nèi)〉降?code>_sel、_imp
的順序和上面buckets
結(jié)構(gòu)體中else
里的順序是一樣的豺总。因?yàn)槲覀儸F(xiàn)在用的是Mac
來(lái)調(diào)試的不是真機(jī)车伞。但是我們打印出來(lái)的內(nèi)容都是空的,這是因?yàn)槲覀儎?chuàng)建代碼的時(shí)候根本沒有創(chuàng)建方法喻喳。cache_t我們已經(jīng)知道是緩存方法的另玖。所以我們加上方法調(diào)用。
上代碼:
ZYPerson.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ZYPerson : NSObject
@property (nonatomic, copy) NSString *hobby;
- (void)zyPersonSay1;
- (void)zyPersonSay2;
- (void)zyPersonSay3;
- (void)zyPersonSay4;
- (void)zyPersonSay5;
+ (void)zyPersonSayHappy;
@end
ZYPerson.m
#import "ZYPerson.h"
@implementation ZYPerson
- (instancetype)init{
if (self == [super init]) {
NSLog(@"ZYPerson 初始化: %@",self);
return self;
}
return nil;
}
- (void)zyPersonSay1{
NSLog(@"%s",__func__);
}
- (void)zyPersonSay2{
NSLog(@"%s",__func__);
}
- (void)zyPersonSay3{
NSLog(@"%s",__func__);
}
- (void)zyPersonSay4{
NSLog(@"%s",__func__);
}
- (void)zyPersonSay5{
NSLog(@"%s",__func__);
}
+ (void)zyPersonSayHappy
{
NSLog(@"%s",__func__);
}
@end
main.m
#import <Foundation/Foundation.h>
#import "ZYPerson.h"
#import "ZYIoser.h"
//cache_t
// class_data_bits_t
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZYPerson *p = [ZYPerson alloc];
Class pClass = [ZYPerson class];
NSLog(@"%@",pClass);
[p zyPersonSay1];
}
return 0;
}
重新運(yùn)行代碼表伦,在[p zyPersonSay1];
后一行打上斷點(diǎn)谦去。
(lldb) x/4gx pClass
0x100008708: 0x0000000100008730 0x000000010036a140
0x100008718: 0x0000000101512a80 0x0001801800000003
(lldb) p/x 0x100008708+0x10
(long) $1 = 0x0000000100008718
(lldb) p (cache_t *)0x0000000100008718
(cache_t *) $2 = 0x0000000100008718
(lldb) p *$2
(cache_t) $3 = {
_bucketsAndMaybeMask = {
std::__1::atomic<unsigned long> = {
Value = 4317063808
}
}
= {
= {
_maybeMask = {
std::__1::atomic<unsigned int> = {
Value = 3
}
}
_flags = 32792
_occupied = 1
}
_originalPreoptCache = {
std::__1::atomic<preopt_cache_t *> = {
Value = 0x0001801800000003
}
}
}
}
(lldb) p $3.buckets()
(bucket_t *) $4 = 0x0000000101512a80
(lldb) p *$4
(bucket_t) $5 = {
_sel = {
std::__1::atomic<objc_selector *> = "" {
Value = ""
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 48136
}
}
}
(lldb)
發(fā)現(xiàn)bucket_t
里_imp
的Value
有值了Value = 48136
,而且我們看到_occupied
蹦哼、Value
都有值了鳄哭,之前是沒有的。到這里我們思考一下纲熏,既然buckets
是個(gè)復(fù)數(shù)的存在妆丘,那我們嘗試取一下他的其他位置的值锄俄。利用內(nèi)存平移的方式。如下:
(lldb) p $3.buckets()[1]
(bucket_t) $6 = {
_sel = {
std::__1::atomic<objc_selector *> = (null) {
Value = nil
}
}
_imp = {
std::__1::atomic<unsigned long> = {
Value = 0
}
}
}
發(fā)現(xiàn)可以取勺拣,值不夠在1
號(hào)位上沒有值奶赠。
到這里我們還是沒有看到我們想要的的東西就是zyPersonSay1
。那么我們回到bucket
里去找找药有,看看有沒有相應(yīng)的方法根據(jù)這個(gè)獲取到的value = 48136
來(lái)取到方法名稱毅戈。
struct bucket_t {
//省略了前面的代碼
public:
static inline size_t offsetOfSel() { return offsetof(bucket_t, _sel); }
inline SEL sel() const { return _sel.load(memory_order_relaxed); }
//省略了后面的代碼
}
我們?cè)谥虚g部分找到了上面這兩句代碼,有一個(gè)SEL sel()
塑猖。我們嘗試用一下竹祷。
(lldb) p $5.sel()
(SEL) $10 = "zyPersonSay1"
(lldb)
發(fā)現(xiàn)確實(shí)可以打印出來(lái)了 zyPerosnSay1
方法。
那我到這里我們拿到了sel:$10 = "zyPersonSay1"
羊苟,還剩下一個(gè)imp
塑陵。我們下面再次回到源碼找方法±看能不能拿到imp
令花。
struct bucket_t {
//省略了前面的代碼
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
#define MAYBE_UNUSED_ISA
#else
#define MAYBE_UNUSED_ISA __attribute__((unused))
#endif
inline IMP rawImp(MAYBE_UNUSED_ISA objc_class *cls) const {
uintptr_t imp = _imp.load(memory_order_relaxed);
if (!imp) return nil;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
imp ^= (uintptr_t)cls;
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
#else
#error Unknown method cache IMP encoding.
#endif
return (IMP)imp;
}
inline IMP imp(UNUSED_WITHOUT_PTRAUTH bucket_t *base, Class cls) const {
uintptr_t imp = _imp.load(memory_order_relaxed);
if (!imp) return nil;
#if CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_PTRAUTH
SEL sel = _sel.load(memory_order_relaxed);
return (IMP)
ptrauth_auth_and_resign((const void *)imp,
ptrauth_key_process_dependent_code,
modifierForSEL(base, sel, cls),
ptrauth_key_function_pointer, 0);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_ISA_XOR
return (IMP)(imp ^ (uintptr_t)cls);
#elif CACHE_IMP_ENCODING == CACHE_IMP_ENCODING_NONE
return (IMP)imp;
#else
#error Unknown method cache IMP encoding.
#endif
}
//省略了后面的代碼
}
我們?cè)谡?code>sel()方法的下方就看到了 上面這一段代碼,但是我們發(fā)現(xiàn)這個(gè)方法需要傳一個(gè)cls
和另一個(gè)的值但是我們不知道第一個(gè)傳什么我們就嘗試傳nil
凉倚。那我們嘗試傳入我們的ZYPerson
類pClass
兼都。
(lldb) p $4->imp(nil,pClass)
(IMP) $7 = 0x0000000100003b00 (KCObjcBuild`-[ZYPerson zyPersonSay1])
(lldb)
這樣我們就拿到了我們要的imp:$7 = 0x0000000100003b00
。
模擬底層源碼結(jié)構(gòu)利用代碼直接NSLog打印獲取
當(dāng)我們只有一份不能編譯
的objc源碼
的時(shí)候我們就沒有辦法去利用個(gè)第一種方法去利用lldb
在源碼
里打印調(diào)試稽寒。這時(shí)我們可以創(chuàng)建一個(gè)普通的工程扮碧。同樣創(chuàng)建ZYPerson
類。設(shè)置屬性
杏糙、方法
等慎王。然后我們對(duì)著這份不能編譯的objc 源碼
來(lái)構(gòu)造方法利用NSLog
來(lái)打印。
ZYPerson.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ZYPerson : NSObject
@property (nonatomic, copy) NSString *hobby;
- (void)zyPersonSay1;
- (void)zyPersonSay2;
- (void)zyPersonSay3;
- (void)zyPersonSay4;
- (void)zyPersonSay5;
@end
NS_ASSUME_NONNULL_END
ZYPerson.m
#import "ZYPerson.h"
@implementation ZYPerson
- (instancetype)init{
if (self == [super init]) {
NSLog(@"ZYPerson 初始化: %@",self);
return self;
}
return nil;
}
- (void)zyPersonSay1{
NSLog(@"%s",__func__);
}
- (void)zyPersonSay2{
NSLog(@"%s",__func__);
}
- (void)zyPersonSay3{
NSLog(@"%s",__func__);
}
- (void)zyPersonSay4{
NSLog(@"%s",__func__);
}
- (void)zyPersonSay5{
NSLog(@"%s",__func__);
}
@end
mian.m
#import <Foundation/Foundation.h>
#import "ZYPerson.h"
//cache_t
// class_data_bits_t
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZYPerson *p = [ZYPerson alloc];
Class pClass = [ZYPerson class];
NSLog(@"%@",pClass);
[p zyPersonSay1];
}
return 0;
}
首先我們回到不能編譯的objc
源碼宏侍,我們要的cache
在類里即objc_class
里那我們先模仿造一個(gè)objc_class
的數(shù)據(jù)結(jié)構(gòu)赖淤。
objc_class
源碼結(jié)構(gòu):
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc
};
把源碼里的objc_class
數(shù)據(jù)結(jié)構(gòu)主體復(fù)制過(guò)來(lái)。(為了和系統(tǒng)的名字區(qū)分我們前綴用zy_
)
改造后zy_objc_class
結(jié)構(gòu):
struct zy_objc_class {
Class isa;
Class superclass;
struct zy_cache_t cache;
struct zy_class_data_bits_t bits;
};
這個(gè)時(shí)候cache_t
谅河、class_data_bits_t
報(bào)錯(cuò)咱旱,我們就用上面同樣的方法針對(duì)cache_t
來(lái)造一個(gè)數(shù)據(jù)結(jié)構(gòu),因?yàn)?code>explicit_atomic<uintptr_t> _bucketsAndMaybeMask找不到并且其實(shí)就是一個(gè)uintptr_t
類型的泛型
那就改造成uintptr_t _bucketsAndMaybeMask
,因?yàn)槲覀兊碾娔X必定是支持__LP64__
的所以我們可以把聯(lián)合體只保留結(jié)構(gòu)體部分(互斥绷耍,所以保留一個(gè)就好了)
cache_t 源碼結(jié)構(gòu):
struct cache_t {
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;
};
};
第一步改造后的zy_cache_t
:
struct zy_cache_t {
uintptr_t _bucketsAndMaybeMask;
struct {
mask_t _maybeMask;
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
};
這個(gè)時(shí)候我們可以發(fā)現(xiàn)里面和外面都是struct
那我們干脆合并且我們發(fā)現(xiàn)mask_t
找不到那我們看看mask_t
是什么數(shù)據(jù)結(jié)構(gòu)吐限。
源碼mask_t
數(shù)據(jù)結(jié)構(gòu):
#if __LP64__
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif
第二步改造后的zy_cache_t
:
typedef uint32_t mask_t; // x86_64 & arm64 asm are less efficient with 16-bits
struct zy_cache_t {
uintptr_t _bucketsAndMaybeMask;
mask_t _maybeMask;
uint16_t _flags;
uint16_t _occupied;
};
這個(gè)時(shí)候我們還剩下一個(gè)找不到的class_data_bits_t
。我們繼續(xù)造數(shù)據(jù)結(jié)構(gòu)
源碼class_data_bits_t
struct class_data_bits_t {
uintptr_t bits;
};
改造后zy_class_data_bits_t
struct zy_class_data_bits_t {
uintptr_t bits;
};
總體改造后的zy_objc_class
struct zy_objc_class {
// Class ISA;
Class superclass;
struct zy_cache_t cache;
struct zy_class_data_bits_t bits;
};
因?yàn)槲覀冎饕谦@取zy_cache_t
锨天,我們也知道cache_t
里我們主要是獲取里面的buckets
毯盈。所以我們還需要對(duì)zy_Cache_t
進(jìn)行進(jìn)一步改造。
源碼bucket_t
struct bucket_t {
// 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
}
改造后bucket_t
struct zy_bucket_t {
// 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__
IMP _imp;
SEL _sel;
#else
SEL _sel;
IMP _imp;
#endif
};
至此病袄,我們前期的準(zhǔn)備工作已經(jīng)完成搂赋。我們先來(lái)嘗試一下赘阀,我們知道cache里有一個(gè)occupied還有一個(gè)maybeMask。我們先來(lái)嘗試打印下這兩個(gè)看代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZYPerson *p = [ZYPerson alloc];
Class pClass = [ZYPerson class];
[p zyPersonSay1];
struct zy_objc_class *zy_class = (__bridge struct zy_objc_class *)(pClass);
NSLog(@"_occupied------%hu-----%u",zy_class->cache._occupied,zy_class->cache._maybeMask);
}
return 0;
}
2021-06-28 17:22:45.290892+0800 ZYProjectSix000[54264:4774709] -[ZYPerson zyPersonSay1]
2021-06-28 17:22:45.291334+0800 ZYProjectSix000[54264:4774709] _occupied------1-----3
Program ended with exit code: 0
發(fā)現(xiàn)是真的可以哦脑奠。那我們下面就嘗試獲取我們需要的東西了基公。
我們for循環(huán)來(lái)獲取bucket
。但是獲取bucket我們不知道怎么獲取宋欺,所以我們回到源碼查看系統(tǒng)怎么獲取的轰豆。
struct cache_t {
//省略前面大量代碼
unsigned capacity() const;
struct bucket_t *buckets() const;
Class cls() const;
//省略后面大量代碼
}
跟蹤進(jìn)去:
struct bucket_t *cache_t::buckets() const
{
uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
return (bucket_t *)(addr & bucketsMask);
}
發(fā)現(xiàn)是從_bucketsAndMaybeMask
里 load
獲取的,這個(gè)我們還是沒法處理齿诞。那我們就直接改變cache_t結(jié)構(gòu)里面的_bucketsAndMaybeMask
酸休,改造后:
struct zy_cache_t {
struct zy_bucket_t * _bukets;
mask_t _maybeMask;
uint16_t _flags;
uint16_t _occupied;
};
接下來(lái)我們利用for循環(huán)來(lái)遍歷我們的zy_cache_t
來(lái)獲取_buket
。
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZYPerson *p = [ZYPerson alloc];
Class pClass = [ZYPerson class];
[p zyPersonSay1];
[p zyPersonSay2];
[p zyPersonSay3];
struct zy_objc_class *zy_class = (__bridge struct zy_objc_class *)(pClass);
NSLog(@"_occupied------%hu-----%u",zy_class->cache._occupied,zy_class->cache._maybeMask);
//zyPersonSay1------1-----3
for (mask_t i = 0; i < zy_class->cache._maybeMask; i++) {
struct zy_bucket_t bucket = zy_class->cache._bukets[I];
NSLog(@"zy_bucket_t==== %@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
}
}
return 0;
}
2021-06-28 18:18:58.181204+0800 ZYProjectSix000[55066:4808195] -[ZYPerson zyPersonSay1]
2021-06-28 18:18:58.181646+0800 ZYProjectSix000[55066:4808195] -[ZYPerson zyPersonSay2]
2021-06-28 18:18:58.181709+0800 ZYProjectSix000[55066:4808195] -[ZYPerson zyPersonSay3]
2021-06-28 18:18:58.181747+0800 ZYProjectSix000[55066:4808195] _occupied------1-----7
2021-06-28 18:18:58.181789+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
2021-06-28 18:18:58.181871+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
2021-06-28 18:18:58.181903+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
2021-06-28 18:18:58.181926+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
2021-06-28 18:18:58.182029+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== zyPersonSay3 - 0xbe70
2021-06-28 18:18:58.182091+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
2021-06-28 18:18:58.182132+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
可以看到確實(shí)可以打印出來(lái)我們調(diào)用的方法祷杈。這種方式的調(diào)試打印有以下幾個(gè)優(yōu)點(diǎn):
1斑司,源碼無(wú)法調(diào)試,找到替方法但汞。
2宿刮,解決了LLDB
出現(xiàn)錯(cuò)誤就需要重新寫一遍lldb
命令從頭再來(lái)一邊的苦惱。
3私蕾,這種還實(shí)現(xiàn)了小規(guī)模取樣僵缺,從而幫助我們更清晰分析源碼。
Cache_t原理探索
從上面的打印我們看出第一次嘗試打印的時(shí)候打印出來(lái)的_occupied
踩叭、_maybeMask
是1-----3
磕潮,而第二次加入了[p zyPersonSay2]
; [p zyPersonSay3]
;之后就變成了1-----7
。并且只打印了zyPersonSay3一個(gè)方法容贝,那其他的方法哪里去了揉抵?到這里我們不妨嘗試下 加入一個(gè)類方法:
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZYPerson *p = [ZYPerson alloc];
Class pClass = [ZYPerson class];
[p zyPersonSay1];
[p zyPersonSay2];
[p zyPersonSay3];
[ZYPerson zyPersonSayHappy];
struct zy_objc_class *zy_class = (__bridge struct zy_objc_class *)(pClass);
NSLog(@"_occupied------%hu-----%u",zy_class->cache._occupied,zy_class->cache._maybeMask);
//zyPersonSay1------1-----3
for (mask_t i = 0; i < zy_class->cache._maybeMask; i++) {
struct zy_bucket_t bucket = zy_class->cache._bukets[I];
NSLog(@"zy_bucket_t==== %@ - %p",NSStringFromSelector(bucket._sel),bucket._imp);
}
}
return 0;
}
2021-06-28 18:18:58.181204+0800 ZYProjectSix000[55066:4808195] -[ZYPerson zyPersonSay1]
2021-06-28 18:18:58.181646+0800 ZYProjectSix000[55066:4808195] -[ZYPerson zyPersonSay2]
2021-06-28 18:18:58.181709+0800 ZYProjectSix000[55066:4808195] -[ZYPerson zyPersonSay3]
2021-06-28 18:18:58.181747+0800 ZYProjectSix000[55066:4808195] zyPersonSay1------1-----7
2021-06-28 18:18:58.181789+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
2021-06-28 18:18:58.181871+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
2021-06-28 18:18:58.181903+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
2021-06-28 18:18:58.181926+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
2021-06-28 18:18:58.182029+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== zyPersonSay3 - 0xbe70
2021-06-28 18:18:58.182091+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
2021-06-28 18:18:58.182132+0800 ZYProjectSix000[55066:4808195] zy_bucket_t==== (null) - 0x0
發(fā)現(xiàn)還是沒有。我們?cè)偌右粋€(gè)zyPersonSay4
,打印:
2021-06-28 18:41:29.779431+0800 ZYProjectSix000[55204:4824980] _occupied------2-----7
2021-06-28 18:41:29.779461+0800 ZYProjectSix000[55204:4824980] zy_bucket_t==== (null) - 0x0
2021-06-28 18:41:29.779535+0800 ZYProjectSix000[55204:4824980] zy_bucket_t==== zyPersonSay4 - 0xbe70
2021-06-28 18:41:29.779575+0800 ZYProjectSix000[55204:4824980] zy_bucket_t==== (null) - 0x0
2021-06-28 18:41:29.779613+0800 ZYProjectSix000[55204:4824980] zy_bucket_t==== (null) - 0x0
2021-06-28 18:41:29.779656+0800 ZYProjectSix000[55204:4824980] zy_bucket_t==== zyPersonSay3 - 0xb9a0
2021-06-28 18:41:29.779748+0800 ZYProjectSix000[55204:4824980] zy_bucket_t==== (null) - 0x0
2021-06-28 18:41:29.779810+0800 ZYProjectSix000[55204:4824980] zy_bucket_t==== (null) - 0x0
發(fā)現(xiàn)只有一個(gè)zyPersonSay3
和zyPersonSay4
嗤疯。但是_occupied
和maybeMask
變成了2-7
。
現(xiàn)在我們把所有方法屏蔽闺兢,只調(diào)用一個(gè)init
方法茂缚。
2021-06-28 18:50:38.244319+0800 ZYProjectSix000[55308:4832732] ZYPerson 初始化: <ZYPerson: 0x100534a60>
2021-06-28 18:50:38.244788+0800 ZYProjectSix000[55308:4832732] +[ZYPerson zyPersonSayHappy]
2021-06-28 18:50:38.244820+0800 ZYProjectSix000[55308:4832732] _occupied------1-----3
2021-06-28 18:50:38.244875+0800 ZYProjectSix000[55308:4832732] zy_bucket_t==== init - 0x7ffe204a5d5df
2021-06-28 18:50:38.244901+0800 ZYProjectSix000[55308:4832732] zy_bucket_t==== (null) - 0x0
2021-06-28 18:50:38.244923+0800 ZYProjectSix000[55308:4832732] zy_bucket_t==== (null) - 0x0
我們發(fā)現(xiàn)這個(gè)init
方法本來(lái)是父類NSObject
的方法。但是這里也有打印屋谭。而且我們看到調(diào)用自己的方法的時(shí)候有緩存方法脚囊,但是并不是所有調(diào)用的方法。而且打印的_occupied
桐磁、mask
并不是我們想的那樣直接遞增的悔耘。這個(gè)規(guī)則是什么?接下來(lái)我們回到objc
源碼 探究下:
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;
};
//省略部分代碼
}
從上面的機(jī)構(gòu)我們可以看到我擂,_bucketsAndMaybeMask
,其實(shí)存的就是buckets
和maybeMask
兩個(gè)數(shù)據(jù)衬以。之前我們不論是lldb調(diào)試 打印還是 自己造數(shù)據(jù)結(jié)構(gòu)打印都是一個(gè)從緩存cache_t
中取/讀
的過(guò)程缓艳,那要搞清楚它讀出來(lái)的東西為什么是這樣的,我們就必須要搞清楚它存/寫
的過(guò)程看峻。
其實(shí)在上面的代碼中我們見過(guò)一個(gè)insert
方法阶淘。如下:
struct cache_t {
//省略前面部分代碼
void insert(SEL sel, IMP imp, id receiver);
void copyCacheNolock(objc_imp_cache_entry *buffer, int len);
void destroy();
void eraseNolock(const char *func);
static void init();
static void collectNolock(bool collectALot);
static size_t bytesForCapacity(uint32_t cap);
//省略后面部分代碼
}
從這段代碼我們看到它調(diào)用insert
方法 傳入的參數(shù)是 sel
、imp
互妓、receiver
溪窒。我們跟蹤進(jìn)去看看:
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
runtimeLock.assertLocked();
// Never cache before +initialize is done
if (slowpath(!cls()->isInitialized())) {
return;
}
if (isConstantOptimizedCache()) {
_objc_fatal("cache_t::insert() called with a preoptimized cache for %s",
cls()->nameForLogging());
}
#if DEBUG_TASK_THREADS
return _collecting_in_critical();
#else
#if CONFIG_USE_CACHE_LOCK
mutex_locker_t lock(cacheUpdateLock);
#endif
ASSERT(sel != 0 && cls()->isInitialized());
// Use the cache as-is if until we exceed our expected fill ratio.
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
if (b[i].sel() == sel) {
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
bad_cache(receiver, (SEL)sel);
#endif // !DEBUG_TASK_THREADS
}
我們看到有一個(gè)mask_t newOccupied = occupied() + 1
,跟蹤進(jìn)去發(fā)現(xiàn)就是一個(gè)賦值返回。這里可以看到newOccupied
第一次來(lái)其實(shí)就是個(gè)初始化過(guò)程結(jié)果為1
冯勉。下面我們看到一個(gè)判斷isConstantEmptyCache
.是否是空的緩存澈蚌,如果是則進(jìn)入if
。很明顯第一次來(lái)會(huì)走這里灼狰。而if里的代碼其實(shí)就是一個(gè)賦值capacity
的過(guò)程宛瞄。而枚舉的INIT_CACHE_SIZE
/* Initial cache bucket count. INIT_CACHE_SIZE must be a power of two. */
enum {
#if CACHE_END_MARKER || (__arm64__ && !__LP64__)
// When we have a cache end marker it fills a bucket slot, so having a
// initial cache size of 2 buckets would not be efficient when one of the
// slots is always filled with the end marker. So start with a cache size
// 4 buckets.
INIT_CACHE_SIZE_LOG2 = 2,
#else
// Allow an initial bucket size of 2 buckets, since a large number of
// classes, especially metaclasses, have very few imps, and we support
// the ability to fill 100% of the cache before resizing.
INIT_CACHE_SIZE_LOG2 = 1,
#endif
INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2),
MAX_CACHE_SIZE_LOG2 = 16,
MAX_CACHE_SIZE = (1 << MAX_CACHE_SIZE_LOG2),
FULL_UTILIZATION_CACHE_SIZE_LOG2 = 3,
FULL_UTILIZATION_CACHE_SIZE = (1 << FULL_UTILIZATION_CACHE_SIZE_LOG2),
};
而INIT_CACHE_SIZE
在arm64
架構(gòu)下 等于1
,并且左移兩位就等于4
伏嗜。然后就走reallocate
方法開辟空間坛悉。跟蹤進(jìn)去:
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
bucket_t *oldBuckets = buckets();
bucket_t *newBuckets = allocateBuckets(newCapacity);
// Cache's old contents are not propagated.
// This is thought to save cache memory at the cost of extra cache fills.
// fixme re-measure this
ASSERT(newCapacity > 0);
ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
collect_free(oldBuckets, oldCapacity);
}
}
發(fā)現(xiàn)里面就是開辟了兩個(gè)桶子oldBuckets
、newBuckets
承绸。然后就是setBucketsAndMask(newBuckets, newCapacity - 1)
裸影。這時(shí)候把新創(chuàng)建的newBuckets
存進(jìn)cache_t
第一位成員的_bucketsAndMaybeMask
里面。
void cache_t::setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask)
{
// objc_msgSend uses mask and buckets with no locks.
// It is safe for objc_msgSend to see new buckets but old mask.
// (It will get a cache miss but not overrun the buckets' bounds).
// It is unsafe for objc_msgSend to see old buckets and new mask.
// Therefore we write new buckets, wait a lot, then write new mask.
// objc_msgSend reads mask first, then buckets.
#ifdef __arm__
// ensure other threads see buckets contents before buckets pointer
mega_barrier();
_bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_relaxed);
// ensure other threads see new buckets before new mask
mega_barrier();
_maybeMask.store(newMask, memory_order_relaxed);
_occupied = 0;
#elif __x86_64__ || i386
// ensure other threads see buckets contents before buckets pointer
_bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_release);
// ensure other threads see new buckets before new mask
_maybeMask.store(newMask, memory_order_release);
_occupied = 0;
#else
#error Don't know how to do setBucketsAndMask on this architecture.
#endif
}
就看到_bucketsAndMaybeMask.store((uintptr_t)newBuckets, memory_order_release);
這句存儲(chǔ)代碼把newBuckets
的地址指針強(qiáng)轉(zhuǎn)uintptr_t
后存在了_bucketsAndMaybeMask
里军熏。并且把_occupied = 0;
因?yàn)楝F(xiàn)在還沒存東西轩猩。所以回到insert
方法里。接著往下走荡澎。
接下來(lái)我們看到創(chuàng)建了一個(gè)bucket
bucket_t *b = buckets();
跟蹤進(jìn)去發(fā)現(xiàn)是從剛才存buckets
的_bucketsAndMaybeMask
里取出來(lái)均践。
struct bucket_t *cache_t::buckets() const
{
uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed);
return (bucket_t *)(addr & bucketsMask);
}
接著 將capacity-1
,這個(gè)時(shí)候的capacity
還是剛開始我們得到的4
,減去了1
之后等于3
,賦值給了m
.其實(shí)這里的m
就是后期我們打印的mask
摩幔。
然后就是進(jìn)行了一下cache_hash
得到一個(gè)地址彤委。
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
跟蹤下cache_hash
:
static inline mask_t cache_hash(SEL sel, mask_t mask)
{
uintptr_t value = (uintptr_t)sel;
#if CONFIG_USE_PREOPT_CACHES
value ^= value >> 7;
#endif
return (mask_t)(value & mask);
}
然后就開始循環(huán)遍歷了,做do while
處理或衡。
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
//省略前面代碼
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
if (b[i].sel() == sel) {
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
bad_cache(receiver, (SEL)sel);
}
看下while條件cache_next
#if CACHE_END_MARKER
static inline mask_t cache_next(mask_t i, mask_t mask) {
return (i+1) & mask;
}
#elif __arm64__
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
#else
#error unexpected configuration
#endif
這里就是為了防止在插入的過(guò)程中查找的位置已經(jīng)存在值了出現(xiàn)hash
沖突所以進(jìn)行了一次再hash
焦影。直到它不等于我們的開始位置。然后進(jìn)行插入數(shù)據(jù)封断。
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
當(dāng)?shù)?code>i 個(gè)bucket
的sel
不存在(即從來(lái)沒有進(jìn)來(lái)緩存過(guò)值)就進(jìn)入if
然后就增加occupied
斯辰。
void cache_t::incrementOccupied()
{
_occupied++;
}
這個(gè)時(shí)候occupied = 1
了。這就是為什么我們調(diào)用一次zyPersonSay1
方法 時(shí)候 occupied == 1坡疼,_maybeMask == 3
彬呻。
然后就利用bucket
的set
方法 插入bucket
、sel
、imp
闸氮、cls()
剪况。
如果不是第一次進(jìn)來(lái) 就判斷sel是否存在,存在就直接返回湖苞,不再存儲(chǔ)拯欧。這就是一次完整的存儲(chǔ)過(guò)程(主要講第一次)。
下面我們來(lái)看看第二次進(jìn)來(lái)的時(shí)候(occupied == 2财骨,_maybeMask == 7
):
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
//省略前面代碼
// Use the cache as-is if until we exceed our expected fill ratio.
mask_t newOccupied = occupied() + 1;
unsigned oldCapacity = capacity(), capacity = oldCapacity;
if (slowpath(isConstantEmptyCache())) {
// Cache is read-only. Replace it.
if (!capacity) capacity = INIT_CACHE_SIZE;
reallocate(oldCapacity, capacity, /* freeOld */false);
}
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
// Cache is less than 3/4 or 7/8 full. Use it as-is.
}
#if CACHE_ALLOW_FULL_UTILIZATION
else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
// Allow 100% cache utilization for small buckets. Use it as-is.
}
#endif
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
//省略后面代碼
}
第一次緩存后occupied == 1镐作,_maybeMask == 3
。 這個(gè)時(shí)候
mask_t newOccupied = occupied() + 1;
后newOccupied = 2
隆箩,并且走else if
判斷该贾。
else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
查看判斷條件中cache_fill_ratio
:
static inline mask_t cache_fill_ratio(mask_t capacity) {
return capacity * 3 / 4;
}
cache_fill_ratio
就是3/4
容積,這個(gè)時(shí)候在判斷里我們得出的剛好是2+ 1 <= 4*3/4
成立。
ps:這里用到了3/4
容積擴(kuò)容算法
捌臊。很多數(shù)組和空間擴(kuò)容方法里都用到這種擴(kuò)容算法杨蛋。
最后走到else
里。
else {
capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
if (capacity > MAX_CACHE_SIZE) {
capacity = MAX_CACHE_SIZE;
}
reallocate(oldCapacity, capacity, true);
}
這里直接把capacity *2
擴(kuò)大了兩倍
理澎。則這個(gè)時(shí)候capacity = 8
逞力。
然后繼續(xù)往下走去擴(kuò)容了
reallocate(oldCapacity, capacity, true);
跟蹤進(jìn)去
ALWAYS_INLINE
void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld)
{
bucket_t *oldBuckets = buckets();
bucket_t *newBuckets = allocateBuckets(newCapacity);
// Cache's old contents are not propagated.
// This is thought to save cache memory at the cost of extra cache fills.
// fixme re-measure this
ASSERT(newCapacity > 0);
ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
collect_free(oldBuckets, oldCapacity);
}
}
發(fā)現(xiàn)這里的擴(kuò)容并不是我們想的一樣直接把第一創(chuàng)建的空間擴(kuò)大2
倍。而是根據(jù)新的newCapacity = 8
創(chuàng)建一個(gè)新的newBuckets
糠爬。并且獲取之前的buckets : oldBuckets
然后:
setBucketsAndMask(newBuckets, newCapacity - 1);
if (freeOld) {
collect_free(oldBuckets, oldCapacity);
}
保存新的newBuckets 并且把上次創(chuàng)建的buckets
清空回收了寇荧,所以新的bucket
里不存在第一次存的方法了。
然后回到insert方法接著往下走:
void cache_t::insert(SEL sel, IMP imp, id receiver)
{
//省略前面代碼
bucket_t *b = buckets();
mask_t m = capacity - 1;
mask_t begin = cache_hash(sel, m);
mask_t i = begin;
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot.
do {
if (fastpath(b[i].sel() == 0)) {
incrementOccupied();
b[i].set<Atomic, Encoded>(b, sel, imp, cls());
return;
}
if (b[i].sel() == sel) {
// The entry was added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
return;
}
} while (fastpath((i = cache_next(i, m)) != begin));
bad_cache(receiver, (SEL)sel);
}
獲取到剛創(chuàng)建的8
個(gè)空間的新的newBuckets
执隧。
bucket_t *b = buckets();
將capacity-1
得到新的mask = 7
揩抡。
mask_t m = capacity - 1;
然后重復(fù)第一次的hash算法
重復(fù)判斷是否存在sel
然后增加 occupied
并且保存bucket
到buckets
。
這也是為什么當(dāng)我們調(diào)用方法到zyPersonSay4
的時(shí)候 occupied == 2镀琉,_maybeMask == 7
峦嗤,因?yàn)閿U(kuò)容和算法導(dǎo)致了變化。
ps:疑問(wèn):為什么不把第一次存的方法復(fù)制到新擴(kuò)容創(chuàng)建的內(nèi)存中呢屋摔?原因是烁设,這種類似數(shù)組平移的方法很消耗內(nèi)存。也很浪費(fèi)時(shí)間钓试。并且這個(gè)方法的數(shù)量不定署尤,當(dāng)數(shù)量大了后效率會(huì)非常低。所以蘋果會(huì)采用這種保存新的方法舍棄舊方法的方式來(lái)保證運(yùn)行的速度和流暢亚侠。
總結(jié): 首次buckets 只有4個(gè)內(nèi)存,當(dāng)我們調(diào)用方法到第三個(gè)方法即
zyPersonSay3
時(shí)候已經(jīng)滿足了3/4 buckets
了俗扇,當(dāng)?shù)谒膫€(gè)方法zyPersonSay4
進(jìn)來(lái)的時(shí)候先去判斷了是否符合擴(kuò)容條件硝烂,這個(gè)時(shí)候剛好符合,就先去創(chuàng)建了一個(gè)有8個(gè)內(nèi)存的新的newbuckets
铜幽,并且把之前存了三個(gè)方法的oldBuckets
清空回收了滞谢。然后把第四個(gè)方法作為newbuckets
的第一個(gè)方法插入保存串稀。這時(shí)候我們讀取 得到的就只有一個(gè)方法。并且得到的occupied == 2._maybeMask == 7狮杨。
補(bǔ)充:
蘋果架構(gòu):
arm64 :真機(jī)
i386:模擬器
x86_64:Mac
所以我們有這樣一張表格:
我們對(duì)照這張表格就知道我們?cè)诳丛创a的時(shí)候如何去根據(jù)那些架構(gòu)的宏定義來(lái)選擇代碼進(jìn)行分析母截。
如下表示支持Unix和Unix類的系統(tǒng)(LInux、Mac OS X)
#if __LP64__
如下:
#define CACHE_MASK_STORAGE_OUTLINED 1
#define CACHE_MASK_STORAGE_HIGH_16 2
#define CACHE_MASK_STORAGE_LOW_4 3
#define CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS 4
#if defined(__arm64__) && __LP64__
#if TARGET_OS_OSX || TARGET_OS_SIMULATOR
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#endif
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
至此橄教,文章也告一段落了清寇。但是這里的東西還只是個(gè)開始,后面還有很多東西需要我們?nèi)ジ?xì)致的分析护蝶。
遇事不決华烟,可問(wèn)春風(fēng)。站在巨人的肩膀上學(xué)習(xí)持灰,如有疏忽或者錯(cuò)誤的地方還請(qǐng)多多指教盔夜。謝謝!