本文首發(fā)自RiverLi的公眾號
Objective-C的動態(tài)性
我們都知道OC是一門動態(tài)性的語言,那么怎么理解動態(tài)性呢劲妙?動態(tài)性是指能將操作推遲到運行時再執(zhí)行,所謂的運行時是指代碼經(jīng)過編譯村缸、鏈接之后纷捞,執(zhí)行的狀態(tài)痢虹。OC的動態(tài)性是由Runtime實現(xiàn)的。Runtime是一套C語言API主儡,封裝著很多動態(tài)性相關(guān)的函數(shù)奖唯。動態(tài)性有很多實踐應(yīng)用,比如:
- 利用關(guān)聯(lián)對象(AssociatedObject)給分類添加屬性
- 遍歷類的所有成員變量糜值。
- 交換方法實現(xiàn)丰捷。
- 利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問題
- 等等
isa詳解
我們知道OC中有類與元類之說坯墨,如下圖是經(jīng)典的對象關(guān)系圖,有圖可知:
- 對于對象來說isa指向其類病往。
-
對于類對象來說isa指向其元類捣染。
下面我們通過閱讀源代碼看對象的內(nèi)部信息。
- 首先停巷,在NSObject.h文件中我們可以看到如下信息
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
- 我們將 main.m通過clang工具可以將OC源碼編譯為CPP代碼
//編譯命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
//main.m
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
return 0;
}
通過觀察 main-arm64.cpp
文件我們可以發(fā)現(xiàn)如下信息:
//1.
typedef struct objc_object NSObject;
//2.
struct NSObject_IMPL {
Class isa;
};
- 通過閱讀runtime源碼耍攘,我們可以獲得如下信息
//objc-runtime-new.h
struct objc_class : objc_object {
Class superclass;
...
}
// objc-private.h
typedef struct objc_class *Class;
typedef struct objc_object *id;
struct objc_object {
private:
isa_t isa;
...
}
我們可以得到如下結(jié)論:
- NSObject其實是struct objc_object類型。
- objc_class繼承自objc_object類型叠穆。
- Class其實是struct objc_class類型少漆。
- objc_object內(nèi)部含有一個isa變量臼膏,其類型是isa_t硼被。
接下來我們繼續(xù)看isa的實現(xiàn),如下:
// objc-private.h
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
// isa.h
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
//objc-class.mm
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
//objc-object.h
inline Class
objc_object::getIsa()
{
if (!isTaggedPointer()) return ISA();
uintptr_t ptr = (uintptr_t)this;
if (isExtTaggedPointer()) {
uintptr_t slot =
(ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
return objc_tag_ext_classes[slot];
} else {
uintptr_t slot =
(ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
return objc_tag_classes[slot];
}
}
//objc-object.h
#if SUPPORT_NONPOINTER_ISA
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
// SUPPORT_NONPOINTER_ISA
#else
// not SUPPORT_NONPOINTER_ISA
inline Class
objc_object::ISA()
{
assert(!isTaggedPointer());
return isa.cls;
}
由上可知isa是由union結(jié)構(gòu)實現(xiàn)的渗磅,在獲取isa內(nèi)容的時候通過SUPPORT_NONPOINTER_ISA宏來指定不同的取法嚷硫。下面看宏的定義
#if !SUPPORT_INDEXED_ISA && !SUPPORT_PACKED_ISA
# define SUPPORT_NONPOINTER_ISA 0
#else
# define SUPPORT_NONPOINTER_ISA 1
#endif
#if __ARM_ARCH_7K__ >= 2 || (__arm64__ && !__LP64__)
# define SUPPORT_INDEXED_ISA 1
#else
# define SUPPORT_INDEXED_ISA 0
#endif
#if (!__LP64__ || TARGET_OS_WIN32 || \
(TARGET_OS_SIMULATOR && !TARGET_OS_IOSMAC))
# define SUPPORT_PACKED_ISA 0
#else
# define SUPPORT_PACKED_ISA 1
#endif
通過參考1、參考2始鱼,我們可以等到在針對arm64的iphone設(shè)備上仔掸,SUPPORT_INDEXED_ISA=0,SUPPORT_PACKED_ISA=0医清,SUPPORT_NONPOINTER_ISA=0, SUPPORT_INDEXED_ISA=0起暮。 從而object_getClass的返回值是 a.bits & ISA_MASK`
isa其他字段意義
- nonpointer:0,代表普通的指針会烙,存儲著Class负懦、Meta-Class對象的內(nèi)存地址。 1柏腻,代表優(yōu)化過纸厉,使用位域存儲更多的信息
- has_assoc:是否有設(shè)置過關(guān)聯(lián)對象,如果沒有五嫂,釋放時會更快
- has_cxx_dtor:是否有C++的析構(gòu)函數(shù)(.cxx_destruct)颗品,如果沒有,釋放時會更快
- shiftcls:存儲著Class沃缘、Meta-Class對象的內(nèi)存地址信息
- magic: 用于在調(diào)試時分辨對象是否未完成初始化
- weakly_referenced:是否有被弱引用指向過躯枢,如果沒有,釋放時會更快
- deallocating:對象是否正在釋放
- extra_rc:里面存儲的值是引用計數(shù)器減1
- has_sidetable_rc:引用計數(shù)器是否過大無法存儲在isa中槐臀,如果為1闺金,那么引用計數(shù)會存儲在一個叫SideTable的類的屬性中。
結(jié)論
- 對象的isa指向類峰档,類的isa指針指向元類败匹,元類的isa指針指向基類寨昙。
- isa是用union結(jié)構(gòu)表示, 根據(jù)不同的平臺isa所指向的對象的地址取法不一樣,如arm64環(huán)境下掀亩,真實地址是:isa.bits & ISA_MASK