詳解:Objective-C Class Ivar Layout
這次探索源于一個(gè)朋友問(wèn)的問(wèn)題,當(dāng)我們定義一個(gè)類的實(shí)例變量的時(shí)候,可以指定其修飾符:
- @interface Sark : NSObject {
- __strong id _gayFriend; // 無(wú)修飾符的對(duì)象默認(rèn)會(huì)加 __strong
- __weak id _girlFriend;
- __unsafe_unretained id _company;
- }
- @end
這使得 ivar (instance variable) 可以像屬性一樣在 ARC 下進(jìn)行正確的引用計(jì)數(shù)管理。
那么問(wèn)題來(lái)了,假如這個(gè)類是動(dòng)態(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);
該如何像上面一樣來(lái)添加 ivar 的屬性修飾符呢?
刨根問(wèn)底了一下,發(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 的對(duì)象類型。
這兩個(gè)值可以通過(guò) runtime 提供的幾個(gè) API 來(lái)訪問(wèn):
- 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)
但我們幾乎沒可能用到這幾個(gè) API,IvarLayout 的值由 runtime 確定,沒必要關(guān)心它的存在,但為了解決上述問(wèn)題,我們?cè)囍平饬?IvarLayout 的編碼方式。
舉個(gè)例子說(shuō)明,若類定義為:
- @interface Foo : NSObject {
- __strong id ivar0;
- __weak id ivar1;
- __weak id ivar2;
- }
- @end
則儲(chǔ)存 strong ivar 的 ivarLayout 的值為 0x012000
儲(chǔ)存 weak ivar 的 weakIvarLayout 的值為 0x1200
一個(gè) uint8_t 在 16 進(jìn)制下是兩位,所以編碼的值每?jī)晌灰粚?duì)兒,以上面的 ivarLayout 為例:
前兩位 01 表示有 0 個(gè)非 strong 對(duì)象和 1 個(gè) strong 對(duì)象
之后兩位 20 表示有 2 個(gè)非 strong 對(duì)象和 0 個(gè) strong 對(duì)象
***兩位 00 為結(jié)束符,就像 cstring 的 \0 一樣
同理,上面的 weakIvarLayout:
前兩位 12 表示有 1 個(gè)非 weak 對(duì)象和接下來(lái)連續(xù) 2 個(gè) weak 對(duì)象
00 結(jié)束符
這樣,用兩個(gè) layout 編碼值就可以排查出一個(gè) ivar 是屬于 strong 還是 weak 的,若都沒有找到,就說(shuō)明這個(gè)對(duì)象是 unsafe_unretained.
做個(gè)練習(xí),若類定義為:
- @interface Bar : NSObject {
- __weak id ivar0;
- __strong id ivar1;
- __unsafe_unretained id ivar2;
- __weak id ivar3;
- __strong id ivar4;
- }
- @end
則儲(chǔ)存 strong ivar 的 ivarLayout 的值為 0x012100
儲(chǔ)存 weak ivar 的 weakIvarLayout 的值為 0x01211000
- 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);
本以為解決了這個(gè)問(wèn)題,但是 runtime 繼續(xù)打臉,strong 和 weak 的內(nèi)存管理并沒有生效,繼續(xù)研究發(fā)現(xiàn), class 的 flags 中有一個(gè)標(biāo)記位記錄這個(gè)類是否 ARC,正常編譯的類,且標(biāo)識(shí)了 -fobjc-arc flag 時(shí),這個(gè)標(biāo)記位為 1,而動(dòng)態(tài)創(chuàng)建的類并沒有設(shè)置它。所以只能繼續(xù)黑魔法,運(yùn)行時(shí)把這個(gè)標(biāo)記位設(shè)置上,探索過(guò)程不贅述了,實(shí)現(xiàn)如下:
- static void fixup_class_arc(Class class) {
- struct {
- Class isa;
- Class superclass;
- struct {
- void *_buckets;
- uint32_t _mask;
- uint32_t _occupied;
- } 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 1flags |= RO_IS_ARR;
- }
把這個(gè) fixup 放在 objc_registerClassPair(class); 之后,這個(gè)動(dòng)態(tài)的類終于可以像靜態(tài)編譯的類一樣操作 ivar 了,可以測(cè)試一下:
- 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 在這里會(huì)釋放大括號(hào)內(nèi)的 girl,boy
- // 輸出:weakIvar 為 nil,strongIvar 有值
- NSLog(@"%@, %@", object_getIvar(sark, weakIvar), object_getIvar(sark, strongIvar));
Done.