iOS底層探索之類的結(jié)構(gòu)—cache分析(上)

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)中,可以看到cachecache_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)lldb調(diào)試

從控制臺(tái)輸出可以看到結(jié)構(gòu)是一模摸一樣樣冕香。查看cache_t的源碼蛹尝,我們還發(fā)現(xiàn)底層分成了3個(gè)架構(gòu)來處理,其中真機(jī)的架構(gòu)中maskbucket是寫在一起悉尾,目的是為了優(yōu)化突那,可以通過各自的掩碼來獲取相應(yīng)的數(shù)據(jù)。

  1. CACHE_MASK_STORAGE_OUTLINED: 表示運(yùn)行的環(huán)境是模擬器 或者 macOS系統(tǒng)
  2. CACHE_MASK_STORAGE_HIGH_16: 表示運(yùn)行環(huán)境是 64位的真機(jī)
  3. 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ǔ)的是SELIMP猫缭,同樣分為兩個(gè)版本葱弟,真機(jī) 和 非真機(jī),區(qū)別在于SELIMP的順序不一致猜丹。
到此大概知道了cache是和方法有關(guān)的了芝加,這家伙就是方法緩存嘛??。

由此可以畫出一個(gè)簡(jiǎn)單的結(jié)構(gòu)圖射窒,如下

image.png

那么到底是不是方法緩存呢藏杖?又是如何進(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è)方法,打印看看胡嘿,是否緩存了方法

bucket_t測(cè)試1

調(diào)用了兩個(gè)方法蛉艾,都打印出來了,那么我們多調(diào)用幾個(gè)方法看看
bucket_t測(cè)試2

從以上兩個(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í)??,提升自我??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末呻拌,一起剝皮案震驚了整個(gè)濱河市葱轩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌藐握,老刑警劉巖靴拱,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異猾普,居然都是意外死亡袜炕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門抬闷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妇蛀,“玉大人耕突,你說我怎么就攤上這事∑兰埽” “怎么了眷茁?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)纵诞。 經(jīng)常有香客問我上祈,道長(zhǎng),這世上最難降的妖魔是什么浙芙? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任登刺,我火速辦了婚禮,結(jié)果婚禮上嗡呼,老公的妹妹穿的比我還像新娘纸俭。我一直安慰自己,他們只是感情好南窗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布揍很。 她就那樣靜靜地躺著,像睡著了一般万伤。 火紅的嫁衣襯著肌膚如雪窒悔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天敌买,我揣著相機(jī)與錄音简珠,去河邊找鬼。 笑死虹钮,一個(gè)胖子當(dāng)著我的面吹牛聋庵,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播芜抒,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼珍策,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了宅倒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤屯耸,失蹤者是張志新(化名)和其女友劉穎拐迁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疗绣,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡线召,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了多矮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缓淹。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哈打,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出讯壶,到底是詐尸還是另有隱情料仗,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布伏蚊,位于F島的核電站立轧,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏躏吊。R本人自食惡果不足惜氛改,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望比伏。 院中可真熱鬧胜卤,春花似錦、人聲如沸赁项。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)肤舞。三九已至紫新,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間李剖,已是汗流浹背芒率。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留篙顺,地道東北人偶芍。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像德玫,于是被迫代替她去往敵國(guó)和親匪蟀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

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