原文出自博客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));