Objective-C Class Ivar Layout 探索

原文出自博客Objective-C Class Ivar Layout 探索
當(dāng)我們定義一個類的實(shí)例變量的時候携栋,可以指定其修飾符:

@interface Sark : NSObject {
  __strong id _gayFriend; // 無修飾符的對象默認(rèn)會加 __strong
  __weak id _girlFriend;
  __unsafe_unretained id _company;
}
@end

這使得 ivar (instance variable) 可以像屬性一樣在 ARC 下進(jìn)行正確的引用計數(shù)管理玻募。

那么問題來了,假如這個類是動態(tài)生成的:

Class class = objc_allocateClassPair(NSObject.class, "Sark", 0);
class_addIvar(class, "_gayFriend", sizeof(id), log2(sizeof(id)), @encode(id));
class_addIvar(class, "_girlFriend", sizeof(id), log2(sizeof(id)), @encode(id));
class_addIvar(class, "_company", sizeof(id), log2(sizeof(id)), @encode(id));
objc_registerClassPair(class);

該如何像上面一樣來添加 ivar 的屬性修飾符呢?

刨根問底了一下异袄,發(fā)現(xiàn) ivar 的修飾信息存放在了 Class 的 Ivar Layout 中:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout; // <- 記錄了哪些是 strong 的 ivar

    const char * name;
    const method_list_t * baseMethods;
    const protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout; // <- 記錄了哪些是 weak 的 ivar
    const property_list_t *baseProperties;
};

ivarLayout 和 weakIvarLayout 分別記錄了哪些 ivar 是 strong 或是 weak咱枉,都未記錄的就是基本類型和 __unsafe_unretained 的對象類型。

這兩個值可以通過 runtime 提供的幾個 API 來訪問:

const uint8_t *class_getIvarLayout(Class cls)
const uint8_t *class_getWeakIvarLayout(Class cls)
void class_setIvarLayout(Class cls, const uint8_t *layout)
void class_setWeakIvarLayout(Class cls, const uint8_t *layout)

但我們幾乎沒可能用到這幾個 API球昨,IvarLayout 的值由 runtime 確定尔店,沒必要關(guān)心它的存在,但為了解決上述問題主慰,我們試著破解了 IvarLayout 的編碼方式嚣州。

舉個例子說明,若類定義為:

@interface Foo : NSObject {
    __strong id ivar0;
    __weak id ivar1;
    __weak id ivar2;
}
@end

則儲存 strong ivar 的 ivarLayout 的值為 0x012000

儲存 weak ivar 的 weakIvarLayout 的值為 0x1200

一個 uint8_t 在 16 進(jìn)制下是兩位河哑,所以編碼的值每兩位一對兒避诽,以上面的 ivarLayout 為例:

前兩位 01 表示有 0 個非 strong 對象和 1 個 strong 對象
之后兩位 20 表示有 2 個非 strong 對象和 0 個 strong 對象
最后兩位 00 為結(jié)束符,就像 cstring 的 \0 一樣
同理璃谨,上面的 weakIvarLayout:

前兩位 12 表示有 1 個非 weak 對象和接下來連續(xù) 2 個 weak 對象
00 結(jié)束符
這樣沙庐,用兩個 layout 編碼值就可以排查出一個 ivar 是屬于 strong 還是 weak 的,若都沒有找到佳吞,就說明這個對象是 unsafe_unretained.

做個練習(xí)拱雏,若類定義為:

@interface Bar : NSObject {
    __weak id ivar0;
    __strong id ivar1;
    __unsafe_unretained id ivar2;
    __weak id ivar3;
    __strong id ivar4;
}
@end

則儲存 strong ivar 的 ivarLayout 的值為 0x012100

儲存 weak ivar 的 weakIvarLayout 的值為 0x01211000

于是乎將 class 的創(chuàng)建代碼增加了兩個 ivarLayout 值的設(shè)置:

Class class = objc_allocateClassPair(NSObject.class, "Sark", 0);
class_addIvar(class, "_gayFriend", sizeof(id), log2(sizeof(id)), @encode(id));
class_addIvar(class, "_girlFriend", sizeof(id), log2(sizeof(id)), @encode(id));
class_addIvar(class, "_company", sizeof(id), log2(sizeof(id)), @encode(id));
class_setIvarLayout(class, (const uint8_t *)"\x01\x12"); // <--- new
class_setWeakIvarLayout(class, (const uint8_t *)"\x11\x10"); // <--- new
objc_registerClassPair(class);

本以為解決了這個問題,但是 runtime 繼續(xù)打臉底扳,strong 和 weak 的內(nèi)存管理并沒有生效铸抑,繼續(xù)研究發(fā)現(xiàn), class 的 flags 中有一個標(biāo)記位記錄這個類是否 ARC衷模,正常編譯的類鹊汛,且標(biāo)識了 -fobjc-arc flag 時,這個標(biāo)記位為 1阱冶,而動態(tài)創(chuàng)建的類并沒有設(shè)置它刁憋。所以只能繼續(xù)黑魔法,運(yùn)行時把這個標(biāo)記位設(shè)置上木蹬,探索過程不贅述了至耻,實(shí)現(xiàn)如下:

static void fixup_class_arc(Class class) {
    struct {
        Class isa;
        Class superclass;
        struct {
            void *_buckets;
#if __LP64__
            uint32_t _mask;
            uint32_t _occupied;
#else
            uint16_t _mask;
            uint16_t _occupied;
#endif
        } cache;
        uintptr_t bits;
    } *objcClass = (__bridge typeof(objcClass))class;
#if !__LP64__
#define FAST_DATA_MASK 0xfffffffcUL
#else
#define FAST_DATA_MASK 0x00007ffffffffff8UL
#endif
    struct {
        uint32_t flags;
        uint32_t version;
        struct {
            uint32_t flags;
        } *ro;
    } *objcRWClass = (typeof(objcRWClass))(objcClass->bits & FAST_DATA_MASK);
#define RO_IS_ARR 1<<7    
    objcRWClass->ro->flags |= RO_IS_ARR;
}

把這個 fixup 放在 objc_registerClassPair(class); 之后,這個動態(tài)的類終于可以像靜態(tài)編譯的類一樣操作 ivar 了,可以測試一下:

id sark = [class new];
Ivar weakIvar = class_getInstanceVariable(class, "_girlFriend");
Ivar strongIvar = class_getInstanceVariable(class, "_gayFriend");
{
    id girl = [NSObject new];
    id boy = [NSObject new];
    object_setIvar(sark, weakIvar, girl);
    object_setIvar(sark, strongIvar, boy);
} // ARC 在這里會釋放大括號內(nèi)的 girl尘颓,boy
// 輸出:weakIvar 為 nil走触,strongIvar 有值
NSLog(@"%@, %@", object_getIvar(sark, weakIvar), object_getIvar(sark, strongIvar));
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市疤苹,隨后出現(xiàn)的幾起案子互广,更是在濱河造成了極大的恐慌,老刑警劉巖痰催,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兜辞,死亡現(xiàn)場離奇詭異,居然都是意外死亡夸溶,警方通過查閱死者的電腦和手機(jī)逸吵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缝裁,“玉大人扫皱,你說我怎么就攤上這事〗莅螅” “怎么了韩脑?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長粹污。 經(jīng)常有香客問我段多,道長,這世上最難降的妖魔是什么壮吩? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任进苍,我火速辦了婚禮,結(jié)果婚禮上鸭叙,老公的妹妹穿的比我還像新娘觉啊。我一直安慰自己,他們只是感情好沈贝,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布杠人。 她就那樣靜靜地躺著,像睡著了一般宋下。 火紅的嫁衣襯著肌膚如雪嗡善。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天学歧,我揣著相機(jī)與錄音罩引,去河邊找鬼。 笑死撩满,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播伺帘,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼昭躺,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了伪嫁?” 一聲冷哼從身側(cè)響起领炫,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎张咳,沒想到半個月后帝洪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡脚猾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年葱峡,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片龙助。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡砰奕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出提鸟,到底是詐尸還是另有隱情军援,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布称勋,位于F島的核電站胸哥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏赡鲜。R本人自食惡果不足惜空厌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蝗蛙。 院中可真熱鬧蝇庭,春花似錦、人聲如沸捡硅。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壮韭。三九已至北发,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間喷屋,已是汗流浹背琳拨。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屯曹,地道東北人狱庇。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓惊畏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親密任。 傳聞我的和親對象是個殘疾皇子颜启,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

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