isa, cache, bits
通過前面一篇從 MachO 加載到對(duì)象創(chuàng)建! 可以了解到:
- 在 alloc 的時(shí)候, 系統(tǒng)會(huì)開辟一片內(nèi)存空間, 最終以指針的形式返回(對(duì)象).
而后便會(huì)初始化(對(duì)象的) isa ==>initIsa(cls)
, 具體可以根據(jù)源碼編譯跟進(jìn)調(diào)試, 作為前面文章一個(gè)補(bǔ)充:
如果只看 Class 可跳過這一步.
編譯 objc-750
首先從官方資源網(wǎng)下載 objc 的源碼, 打開 objc.xcodeproj
文件然后進(jìn)行編譯 :
報(bào)錯(cuò)1:
The i386 architecture is deprecated.
You should update your ARCHS build setting to \
remove the i386 architecture.
(in target 'objc')
'i8386' 架構(gòu)被廢棄, 你應(yīng)該更新你的 ARCHS 編譯設(shè)置項(xiàng), 刪除 'i8386' 架構(gòu).
來到 target objc
的 build setting
, 搜索 ARCHS
, 會(huì)發(fā)現(xiàn)報(bào)錯(cuò)提示的 'i386' 以及 'x86_64', 刪除 'i386' 再編譯 :
報(bào)錯(cuò)2:
The i386 architecture is deprecated.
You should update your ARCHS build setting to \
remove the i386 architecture.
(in target 'objc-trampolines')
同報(bào)錯(cuò)1, 將 target objc-trampolines
的 build setting
下 'i386' 刪除再編譯 (注: 這里可以將 objc-trampolines
和 objc-simulator
兩個(gè) target 刪除, 因?yàn)橐话阌貌坏? :
報(bào)錯(cuò)3
'sys/reason.h' file not found
這里需要將頭文件 (注:需要下載多個(gè)依賴庫, 其中包含想要的頭文件, 比如 reason.h
在 ./xnu-4903.221.2/bsd/sys/reason.h
中, 所以需要下載該庫以獲取頭文件) 包含到任一新建文件夾下, 并將系統(tǒng)頭文件路徑 system header search paths
設(shè)置為該文件夾目錄, 具體可參照:
最新Runtime源碼objc4-750編譯
注: 此處并不需要依次建立文件夾, 直接放到工程目錄下, 把包含的頭文件路徑換成頭文件(即: <sys/reason.h> 修改為 <reason.h>) 也可以, 只要 system header search paths
+ import 頭文件的絕對(duì)路徑能找到就可以.
報(bào)錯(cuò)4
Use of undeclared identifier 'CRGetCrashLogMessage'
查看依賴 CrashReporterClient.h
頭文件, 發(fā)現(xiàn)此處用了宏定義:
#ifdef LIBC_NO_LIBCRASHREPORTERCLIENT
/* Fake the CrashReporterClient API */
#define CRGetCrashLogMessage() 0
#define CRSetCrashLogMessage(x) /* nothing */
#else /* !LIBC_NO_LIBCRASHREPORTERCLIENT */
所以需要在 Build Settings
的 Preprocessor Macros
(預(yù)處理宏)類目中加入 LIBC_NO_LIBCRASHREPORTERCLIENT
環(huán)境變量來使方法生效, 繼續(xù)編譯:
報(bào)錯(cuò)5
clang:-1: linker command failed with exit code 1
這個(gè)報(bào)錯(cuò)很多人摸不著頭腦, 因?yàn)闆]有報(bào)錯(cuò)信息, 只知道是 link 時(shí)報(bào)錯(cuò), 這里有個(gè)技巧:
就是查看編譯日志 (快捷鍵 command 9
), 這里記錄詳細(xì)的報(bào)錯(cuò)信息:
ld: can't open order file:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform
/Developer/SDKs/MacOSX10.14.sdk/AppleInternal/OrderFiles/libobjc.order
clang: error: linker command failed with exit code 1
打不開 order file
文件 (因?yàn)檎也坏?, 別擔(dān)心, 只需要換個(gè)路徑就可以了, 在工程目錄的 other 文件夾下同樣有一份這個(gè)文件, 同樣修改 Build Settings
設(shè)置, 搜索 order file
, 即可看到原來是查找 AppleInternal
目錄下的文件, 替換為工程目錄下的文件路徑繼續(xù)編譯:
報(bào)錯(cuò)6
clang:-1: linker command failed with exit code 1
同樣是 clang 錯(cuò)誤, 這下知道怎么看詳細(xì)的報(bào)錯(cuò)信息了, 那么:
ld: library not found for -lCrashReporterClient
clang: error: linker command failed with exit code 1
報(bào)錯(cuò)找不到該庫文件, 由于我們前面設(shè)置過 LIBC_NO_LIBCRASHREPORTERCLIENT
環(huán)境變量, 所以這個(gè) -lCrashReporterClient
是不需要的, 由于報(bào)錯(cuò)是 linker command, 所以要到 Build Settings 里面搜索 linking
, 可以看見 Other link flags 里面有設(shè)置這樣的 flag (注: 也可以直接搜索 lCrashReporterClient
關(guān)鍵字, 搜索結(jié)果會(huì)把包含所有 lCrashReporterClient
flag 的類目找出來) , 刪除該 flag 即可.
還是繼續(xù)編譯:
報(bào)錯(cuò)7
/xcodebuild:-1: SDK "macosx.internal" cannot be located.
/xcrun:-1: unable to find utility "clang++",
not a developer tool or in PATH
看著 xcodebuild
和 xcrun
報(bào)錯(cuò), 又是一臉懵, 其實(shí)很簡單, 還是查看編譯日志:
+ /usr/bin/xcrun -sdk macosx.internal clang++ \
-Wall -mmacosx-version-min=10.12
-arch x86_64 -std=c++11
PATH
xcodebuild: error: SDK "macosx.internal" cannot be located.
xcrun: error: unable to find utility "clang++", \
not a developer tool or in PATH
xcodebuild: error: "macosx.internal" SDK 找不到, 找不到 "clang++" 命令, 明顯的還有執(zhí)行的命令(如下圖):
原來是在執(zhí)行 script 時(shí)報(bào)錯(cuò), 那么就來到 build phases 中查看執(zhí)行的 script 信息:
set -x
/usr/bin/xcrun -sdk macosx.internal clang++ -Wall \
-mmacosx-version-min=10.12
-arch x86_64 -std=c++11 "${SRCROOT}/markgc.cpp" -o
"${BUILT_PRODUCTS_DIR}/markgc"
"${BUILT_PRODUCTS_DIR}/markgc" "${BUILT_PRODUCTS_DIR}/libobjc.A.dylib"
腳本旨在調(diào)用 clang++
命令, 所以把 macosx.internal 改為 macosx, 使用系統(tǒng)自帶的 clang 命令進(jìn)行編譯,
再編譯:
報(bào)錯(cuò)8
no such public header file: '/tmp/objc.dst/usr/include/objc/ObjectiveC\
.apinotes'
同樣, 查看編譯日志:
由于是在 InstallAPI 時(shí)報(bào)錯(cuò), 所以可以在 build setting 中查詢關(guān)鍵字 InstallAPI
, 直接將 Supports Text-Based InstallAPI
設(shè)置為 NO (注: 這里也可以將 InstallAPI flags
中對(duì)應(yīng)的 flag 刪除以消除編譯錯(cuò)誤, 需要相繼刪除幾個(gè)).
編譯成功
到這里, 編譯已經(jīng)成功了, 那么接下來可以新建測試項(xiàng)目了:
此時(shí)應(yīng)注意, 編譯環(huán)境只是在 mac 下, 所以新建 Target 時(shí)只能選擇 macOS 下的Application 相關(guān)項(xiàng) (Cocoa App
, Game
, Command Line Tool
等), 然后添加對(duì)剛剛配置好的 objc 庫依賴 (如果需要的話).
Class
有源碼文件可見:
Class
定義為 typedef struct objc_class *Class;
, 是一個(gè)指向 objc_class
的結(jié)構(gòu)體指針;
objc_class
包含類的 isa
, 父類, 緩存等信息:
struct objc_class : objc_object {
// Class ISA; 繼承自 objc_object
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
}
依次:
isa
在類實(shí)現(xiàn)方法中 static Class realizeClass(Class cls)
, 在設(shè)置好 cls->superclass
后便會(huì)進(jìn)行 isa
初始化 cls->initClassIsa(metacls)
(文章開頭已給出鏈接, 不作贅述):
最終調(diào)用:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) {
if (!nonpointer) {
isa.cls = cls;
} else {
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
}
對(duì) isa
簡單初始化并賦值 cls
的信息, 那么 isa
到底是什么?
解析 isa
(附 superClass
):
調(diào)用如下函數(shù)(方法):
// isa 流程圖驗(yàn)證
int isaTest() {
WXPerson *person = [[WXPerson alloc] init];
Class cls = person.class; // 類對(duì)象
Class cls4 = object_getClass(cls); // 元類
Class cls5 = object_getClass(cls4); // 根元類
NSLog(@"%@ -- %p 對(duì)象", person, person);
NSLog(@"%@ -- %p 類對(duì)象", cls,cls);
NSLog(@"%@ -- %p 元類", cls4, cls4);
NSLog(@"%@ -- %p 根元類", cls5, cls5); // 根元類
NSLog(@"%@ -- %p 對(duì)象父類", person.superclass, person.superclass);
NSLog(@"%@ -- %p 類對(duì)象父類",[cls superclass] , [cls superclass]);
NSLog(@"%@ -- %p 元類父類", [cls4 superclass], [cls4 superclass]);
NSLog(@"%@ -- %p 根元類父類", [cls5 superclass], [cls5 superclass]);
NSLog(@"%@ -- %p 根元類的isa", object_getClass(cls5),
object_getClass(cls5));
NSLog(@"%@ -- %p NSObject 父類", [[[NSObject alloc] init] superclass],
[[[NSObject alloc] init] superclass]);
NSLog(@"%@ -- %p NSObject isa", object_getClass([NSObject class]),
object_getClass([NSObject class]));
return 0;
}
這里直接貼下 log :
2019-04-03 11:36 [51997:6246754] <WXPerson: 0x100f4bd20> --0x100f4bd20 對(duì)象
2019-04-03 11:36 [51997:6246754] WXPerson -- 0x1000026d8 類對(duì)象
2019-04-03 11:36 [51997:6246754] WXPerson -- 0x1000026b0 元類
2019-04-03 11:36 [51997:6246754] NSObject -- 0x100b170f0 根元類
2019-04-03 11:36 [51997:6246754] WXHuman -- 0x100002688 對(duì)象父類
2019-04-03 11:36 [51997:6246754] WXHuman -- 0x100002688 類對(duì)象父類
2019-04-03 11:36 [51997:6246754] WXHuman -- 0x100002660 元類父類
2019-04-03 11:36 [51997:6246754] NSObject -- 0x100b17140 根元類父類
2019-04-03 11:36 [51997:6246754] NSObject -- 0x100b170f0 根元類的isa
2019-04-03 11:36 [51997:6246754] (null) -- 0x0 NSObject 父類
2019-04-03 11:36 [51997:6246754] NSObject -- 0x100b170f0 NSObject isa
這里在貼一張根據(jù)打印地址畫的一張 isa
走向圖 (地址對(duì)號(hào)入座)
這里引入一個(gè)虛擬類 元類
, 元類地址可以通過 objc_getMetaClass
得到.
元類 :
通過實(shí)例對(duì)象(person
)的 class
方法可以查看對(duì)象所屬類(Person
), 根據(jù)對(duì)象的 isa
可知, 類仍然是一個(gè)對(duì)象, 只是這個(gè)對(duì)象是相對(duì)于元類而言:
對(duì)象 --> 類 == 類對(duì)象 --> 元類
元類什么時(shí)候初始化 ?
運(yùn)用 objc/runtime
的接口 objc_allocateClassPair
動(dòng)態(tài)創(chuàng)建類, 通過源碼可以看到, 該接口在創(chuàng)建類的時(shí)候, 定義了兩個(gè) Class
對(duì)象(結(jié)構(gòu)體) cls
和 meta
, 然后對(duì)兩個(gè)類對(duì)象作空間開辟操作:
Class objc_allocateClassPair(Class superclass,
const char *name,
size_t extraBytes)
{
Class cls, meta;
mutex_locker_t lock(runtimeLock);
// Fail if the class name is in use.
// Fail if the superclass isn't kosher.
if (getClass(name) || !verifySuperclass(superclass, true/*rootOK*/)) {
return nil;
}
// Allocate new classes.
// 僅僅只開辟空間, 開辟一個(gè) objc_class 結(jié)構(gòu)體大小的空間作為返回.
// _calloc_class(sizeof(objc_class) + extraBytes);
cls = alloc_class_for_subclass(superclass, extraBytes);
meta = alloc_class_for_subclass(superclass, extraBytes);
// fixme mangle the name if it looks swift-y?
objc_initializeClassPair_internal(superclass, name, cls, meta);
return cls;
}
其次會(huì)調(diào)用到 objc_initializeClassPair_internal
函數(shù):
static void objc_initializeClassPair_internal(Class superclass,
const char *name,
Class cls,
Class meta)
{
runtimeLock.assertLocked();
class_ro_t *cls_ro_w, *meta_ro_w;
// 開辟類/元類 data / ro
cls->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
meta->setData((class_rw_t *)calloc(sizeof(class_rw_t), 1));
cls_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
meta_ro_w = (class_ro_t *)calloc(sizeof(class_ro_t), 1);
cls->data()->ro = cls_ro_w;
meta->data()->ro = meta_ro_w;
// Set basic info
// 查詢 RW_ FLAGS
cls->data()->flags = RW_CONSTRUCTING |
RW_COPIED_RO |
RW_REALIZED |
RW_REALIZING;
meta->data()->flags = RW_CONSTRUCTING |
RW_COPIED_RO |
RW_REALIZED |
RW_REALIZING;
cls->data()->version = 0;
meta->data()->version = 7;
cls_ro_w->flags = 0;
meta_ro_w->flags = RO_META;
if (!superclass) {
cls_ro_w->flags |= RO_ROOT;
meta_ro_w->flags |= RO_ROOT;
}
if (superclass) {
cls_ro_w->instanceStart = superclass->unalignedInstanceSize();
meta_ro_w->instanceStart =
superclass->ISA()->unalignedInstanceSize();
cls->setInstanceSize(cls_ro_w->instanceStart);
meta->setInstanceSize(meta_ro_w->instanceStart);
} else {
cls_ro_w->instanceStart = 0;
meta_ro_w->instanceStart = (uint32_t)sizeof(objc_class);
cls->setInstanceSize((uint32_t)sizeof(id)); // just an isa
meta->setInstanceSize(meta_ro_w->instanceStart);
}
cls_ro_w->name = strdupIfMutable(name);
meta_ro_w->name = strdupIfMutable(name);
cls_ro_w->ivarLayout = &UnsetLayout;
cls_ro_w->weakIvarLayout = &UnsetLayout;
meta->chooseClassArrayIndex();
cls->chooseClassArrayIndex();
// 這里是上面 isa 及 superclass 走位圖的根源!
// Connect to superclasses and metaclasses
// 初始化類對(duì)象的 isa 為 meta 類
cls->initClassIsa(meta);
if (superclass) {
// 元類的 isa 指向 元類的元類(根元類)
meta->initClassIsa(superclass->ISA()->ISA());
cls->superclass = superclass;
meta->superclass = superclass->ISA(); // 子元類的父類 是 父元類
addSubclass(superclass, cls);
addSubclass(superclass->ISA(), meta); // 父類的元類 是 子類元類的父類
} else {
meta->initClassIsa(meta); // 根元類指向本身
cls->superclass = Nil; // 根類指向 Nil
meta->superclass = cls; // 根元類指向根類
// 類關(guān)系 鏈表
// NSObject->nil
// 通過 mask 獲取 bits.data() 將 _firstRealizedClass 設(shè)置為 cls
// cls->data()->nextSiblingClass = _firstRealizedClass;
// _firstRealizedClass = cls;
addRootClass(cls);
// NSObject(元類) nextSiblingClass -> NSObject firstSubclass
// NSObject firstSubclass -> NSObject(元類)
// 將子類的 next 指向父類 first
// cls(父類)的 firstSubclass 設(shè)置為 meta(子類)
// subcls->data()->nextSiblingClass =
// supercls->data()->firstSubclass;
// supercls->data()->firstSubclass = subcls;
// NSObject(meta) 的 父類是 NSObject, 即: 根元類的父類是 NSObject;
addSubclass(cls, meta);
}
// 初始化 cache
cls->cache.initializeToEmpty();
meta->cache.initializeToEmpty();
// 內(nèi)部遞歸 meta 類, 所以不需要寫 addClassTableEntry(meta);
addClassTableEntry(cls);
}
這樣, 相信你應(yīng)該知道元類到底是什么了:
- 元類也是
Class
; - 子元類的
isa
是 父元類 的 元類, 即: 根元類; - 根元類的
isa
指向自己; - 根類的父類是
nil
; - 子類的元類的父類是父類的元類;
- 根元類的父類是根類;
isa 作用
那么 isa 的作用是什么呢?
- 查找類的實(shí)現(xiàn):
在動(dòng)態(tài)創(chuàng)建類時(shí)還需要調(diào)用另外一個(gè)接口:objc_registerClassPair(Class cls)
:
objc_registerClassPair(Class cls) {
// 改變標(biāo)記值 正在創(chuàng)建 -> 已創(chuàng)建
cls->ISA()->changeInfo(RW_CONSTRUCTED, \
RW_CONSTRUCTING | RW_REALIZING);
cls->changeInfo(RW_CONSTRUCTED,
RW_CONSTRUCTING | RW_REALIZING);
// Add to named class table.
addNamedClass(cls, cls->data()->ro->name);
}
該接口會(huì)將注冊(cè)的 cls
以 key-value
的形式添加到 gdb_objc_realized_classes
表中 :
// 添加類實(shí)現(xiàn)到 hash 表中
static void addNamedClass(Class cls,
const char *name,
Class replacing = nil) {
// 插入到 NXMapTable hash 表中, 以 name 作為 key, cls 作為 value 保存
NXMapInsert(gdb_objc_realized_classes, name, cls);
}
而 gdb_objc_realized_classes
表在 load_images
(dyld 注冊(cè)的回調(diào)) -> read_images
第一次的時(shí)候就會(huì)初始化, 在查找類實(shí)現(xiàn)的時(shí)候會(huì)通過 name(類名) 從該表中進(jìn)行查找, 即 getClass(const char *name)
:
static Class getClass(const char *name) {
runtimeLock.assertLocked();
// Try name as-is
Class result = getClass_impl(name);
if (result) return result;
}
static Class getClass_impl(const char *name)
{
runtimeLock.assertLocked();
// Try runtime-allocated table
Class result = (Class)NXMapGet(gdb_objc_realized_classes, name);
if (result) return result;
// Try table from dyld shared cache
return getPreoptimizedClass(name);
}
因?yàn)槭谦@取類的實(shí)現(xiàn), 而獲取 類實(shí)現(xiàn) 在 objc/runtime
中, 即: objc_getClass
:
Class objc_getClass(const char *aClassName) {
return look_up_class(aClassName, NO, YES);
}
look_up_class(const char *name,
bool includeUnconnected __attribute__((unused)),
bool includeClassHandler __attribute__((unused))) {
Class result;
bool unrealized;
{
mutex_locker_t lock(runtimeLock);
result = getClass(name);
unrealized = result && !result->isRealized();
}
if (unrealized) {
mutex_locker_t lock(runtimeLock);
realizeClass(result);
}
return result;
}
可以看到, 在 look_up_class
函數(shù)中會(huì)調(diào)用 getClass
接口去獲取類的實(shí)現(xiàn)!
- 方法查找:
在調(diào)用類方法的時(shí)候,receiver
由編譯器編譯成objc_getClass
(類對(duì)象) 獲取類的實(shí)現(xiàn)(通過匯編取isa
找元類, 其實(shí)是找類對(duì)象的實(shí)現(xiàn)), 然后在元類中找方法的 IMP;
而調(diào)用對(duì)象方法時(shí), 則會(huì)在通過isa
獲取對(duì)象的class
(即類對(duì)象, 其實(shí)是找對(duì)象的實(shí)現(xiàn)) 中查找方法的IMP
;
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, $0 // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
// 64-bit packed isa
and p16, $0, #ISA_MASK
#else
// 32-bit raw isa
mov p16, $0
#endif
.endmacro
至此, 可以了解到, isa
的作用之一是查找類的實(shí)現(xiàn)(class_impl
), 而且, 在調(diào)用方法的時(shí)候并沒有區(qū)分類方法/對(duì)象方法, 只是通過 isa
去類對(duì)象中查找方法的 IMP
.
cache
首先看下 cache_t 結(jié)構(gòu):
mask:緩存 bucket 的總數(shù).
occupied:目前實(shí)際占用的緩存 bucket 的個(gè)數(shù)巴比。
buckets:hash 表钳枕,用來緩存方法,bucket_t 類型,包含 key(sel 方法編號(hào)) 以及方法實(shí)現(xiàn) IMP。
看該結(jié)構(gòu)體提供方法就可以知道, cache_t 是以 hash 表的方式存儲(chǔ)了方法的 IMP,
將 sel 方法編號(hào)轉(zhuǎn)為 cache_key_t 類型即 uintptr_t 作為 key 存儲(chǔ).
typedef uintptr_t cache_key_t;
cache_key_t getKey(SEL sel)
{
assert(sel);
return (cache_key_t)sel;
}
在調(diào)用方法時(shí), objc_msgSend 會(huì)先找 cache, 指的就是這里的方法緩存列表.
bits
bits 結(jié)構(gòu):
可以看到 bits 中 data() 主要存儲(chǔ)了 方法/屬性/協(xié)議 列表, 并且記錄當(dāng)前類的第一個(gè)子類, 以及下一個(gè)類, flags 標(biāo)記當(dāng)前類狀態(tài). version 標(biāo)記當(dāng)前類類型, 如:cls version = 0, meta version = 7,
其他只讀屬性保存在屬性 ro 結(jié)構(gòu)體中.