Cache 分析

1.jpeg

文章開頭我再次貼出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è)成員:ISAsuperclass耳璧、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)單分析如果是緩存屬性那肯定有IMPSEL聚霜。下面我們就看看到底緩存了什么。那么我們到底該查看那個(gè)部分呢珠叔?是_bucketsAndMaybeMask還是_originalPreoptCache還是其他呢蝎宇?我們回到源碼去查看下它提供的功能方法,既然是緩存那肯定是針對(duì)某些東西有增刪改查的操作祷安。

我們跟蹤cache_t方法源碼往下看:

1.png
2.png

我們可以看到上圖紅框標(biāo)出的地方都是在對(duì)bucket_t姥芥、Occupiedmask進(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)就是如下圖:

3.png

接下來(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()之類的方法.

4.png

如上圖我們?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_impValue有值了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凉倚。那我們嘗試傳入我們的ZYPersonpClass兼都。

(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)是從_bucketsAndMaybeMaskload獲取的,這個(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踩叭、_maybeMask1-----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è)zyPersonSay3zyPersonSay4嗤疯。但是_occupiedmaybeMask變成了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í)存的就是bucketsmaybeMask兩個(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ù)是 selimp 互妓、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_SIZEarm64架構(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è)桶子oldBucketsnewBuckets承绸。然后就是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è)bucketsel 不存在(即從來(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彬呻。
然后就利用bucketset方法 插入bucketselimp闸氮、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并且保存bucketbuckets

這也是為什么當(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

所以我們有這樣一張表格:

1.png

我們對(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)多多指教盔夜。謝謝!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末堤魁,一起剝皮案震驚了整個(gè)濱河市喂链,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌妥泉,老刑警劉巖椭微,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異涛漂,居然都是意外死亡赏表,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門匈仗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)瓢剿,“玉大人融涣,你說(shuō)我怎么就攤上這事桥氏。” “怎么了竣付?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵火架,是天一觀的道長(zhǎng)鉴象。 經(jīng)常有香客問(wèn)我,道長(zhǎng)何鸡,這世上最難降的妖魔是什么纺弊? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮骡男,結(jié)果婚禮上淆游,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好犹菱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布拾稳。 她就那樣靜靜地躺著,像睡著了一般腊脱。 火紅的嫁衣襯著肌膚如雪访得。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天陕凹,我揣著相機(jī)與錄音悍抑,去河邊找鬼。 笑死捆姜,一個(gè)胖子當(dāng)著我的面吹牛传趾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泥技,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼浆兰,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了珊豹?” 一聲冷哼從身側(cè)響起簸呈,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎店茶,沒想到半個(gè)月后蜕便,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贩幻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年轿腺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丛楚。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡族壳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出趣些,到底是詐尸還是另有隱情仿荆,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布坏平,位于F島的核電站拢操,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏舶替。R本人自食惡果不足惜令境,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望顾瞪。 院中可真熱鬧展父,春花似錦返劲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)孵延。三九已至吕漂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尘应,已是汗流浹背惶凝。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留犬钢,地道東北人苍鲜。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像玷犹,于是被迫代替她去往敵國(guó)和親混滔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容