本文首發自RiverLi的公眾號
Objective-C的動態性
我們都知道OC是一門動態性的語言,那麼怎麼理解動態性呢?動態性是指能將操作推遲到執行時再執行,所謂的執行時是指程式碼經過編譯、連結之後,執行的狀態。OC的動態性是由Runtime實現的。Runtime是一套C語言API,封裝著很多動態性相關的函式。動態性有很多實踐應用,比如:
- 利用關聯物件(AssociatedObject)給分類新增屬性
- 遍歷類的所有成員變數。
- 交換方法實現。
- 利用訊息轉發機制解決方法找不到的異常問題
- 等等
isa詳解
我們知道OC中有類與元類之說,如下圖是經典的物件關係圖,有圖可知:
- 對於物件來說isa指向其類。
- 對於類物件來說isa指向其元類。
下面我們通過閱讀原始碼看物件的內部資訊。
- 首先,在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
檔案我們可以發現如下資訊:
//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;
...
}
複製程式碼
我們可以得到如下結論:
- NSObject其實是struct objc_object型別。
- objc_class繼承自objc_object型別。
- Class其實是struct objc_class型別。
- objc_object內部含有一個isa變數,其型別是isa_t。
接下來我們繼續看isa的實現,如下:
// 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結構實現的,在獲取isa內容的時候通過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裝置上,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物件的記憶體地址。 1,代表優化過,使用位域儲存更多的資訊
- has_assoc:是否有設定過關聯物件,如果沒有,釋放時會更快
- has_cxx_dtor:是否有C++的解構函式(.cxx_destruct),如果沒有,釋放時會更快
- shiftcls:儲存著Class、Meta-Class物件的記憶體地址資訊
- magic: 用於在除錯時分辨物件是否未完成初始化
- weakly_referenced:是否有被弱引用指向過,如果沒有,釋放時會更快
- deallocating:物件是否正在釋放
- extra_rc:裡面儲存的值是引用計數器減1
- has_sidetable_rc:引用計數器是否過大無法儲存在isa中,如果為1,那麼引用計數會儲存在一個叫SideTable的類的屬性中。
結論
- 物件的isa指向類,類的isa指標指向元類,元類的isa指標指向基類。
- isa是用union結構表示, 根據不同的平臺isa所指向的物件的地址取法不一樣,如arm64環境下,真實地址是:isa.bits & ISA_MASK