objc_object
Objective-C 所有對象都是 C 語言結(jié)構(gòu)體objc_object,這個結(jié)構(gòu)體中包含一個isa成員變量,不是一個普通的指針,是一個isa_t結(jié)構(gòu)體。
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
// initIsa() should be used to init the isa of new objects only.
// If this object already has an isa, use changeIsa() for correctness.
// initInstanceIsa(): objects with no custom RR/AWZ
// initClassIsa(): class objects
// initProtocolIsa(): protocol objects
// initIsa(): other objects
void initIsa(Class cls /*nonpointer=false*/);
void initClassIsa(Class cls /*nonpointer=maybe*/);
void initProtocolIsa(Class cls /*nonpointer=maybe*/);
void initInstanceIsa(Class cls, bool hasCxxDtor);
}
isa_t
因為iPhone5s之后的設(shè)備是在arm64架構(gòu)下,所以截取arm64架構(gòu)下的代碼,這個結(jié)構(gòu)體是蘋果經(jīng)過優(yōu)化的
armv64: iPhoneX, iPhone 5s-8, iPad Air?—?iPad Pro
armv7 : iPhone3Gs-5c, iPad WIFI(4th gen)
armv6 : iPhone?—?iPhone3G
the above if for real devices
i386 : 32-bit simulator
x86_64 : 64-bit simulator
i386是針對intel通用微處理器32位處理器
x86_64是針對x86架構(gòu)的64位處理器
模擬器32位處理器測試需要i386架構(gòu)
模擬器64位處理器測試需要x86_64架構(gòu)
真機32位處理器需要armv7,或者armv7s架構(gòu)
真機64位處理器需要arm64架構(gòu)
為何要進行優(yōu)化呢,進行了怎樣的優(yōu)化,看看Friday Q&A 2013-09-27: ARM64 and You怎么說的
盡管指針為64位,但在實際使用中,這些位數(shù)并不是都用上了。例如X86-64的Mac OS X系統(tǒng)僅使用了其中的47位。而ARM64上占用得更少,目前只有33位。只要未被系統(tǒng)全部占用,這些指針就能用于存儲數(shù)據(jù)。這是Objective-C Runtime演進史上最重要的變化之一。
Objective-C對象是連續(xù)的內(nèi)存塊,這個內(nèi)存塊中第一個指針大小的部分稱為ISA。一般來說,ISA是一個指向該對象所屬類的指針。不過這么大的空間僅作為指針有點兒浪費,尤其是在64位CPU上。運行iOS的ARM64目前僅使用了一個指針的33位,而其余31位則另作他用。另外,類 指針還需要對齊,這就釋放了另外3位,于是ISA指針中共有34位可另作他用。蘋果的ARM64 Runtime正是利用了這一點使性能有了大幅提升。
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
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)
};
}
Hamster Emporium: [objc explain]: Non-pointer isa - Sealie Software一文對這些bits做了如下解釋
| bits | 作用 |
|---|---|
| nonpointer | 0表示普通isa,1表示non-pointe isa |
| has_assoc | 對象具有或曾經(jīng)具有關(guān)聯(lián)的引用。沒有關(guān)聯(lián)引用的對象可以更快地析構(gòu)。 |
| has_cxx_dtor | 對象有一個C ++或ARC析構(gòu)函數(shù)。 沒有析構(gòu)函數(shù)的對象可以更快地解除分配。 |
| shiftcls | 類指針的非零位 |
| magic | 等于0xd2,調(diào)試器使用它來判斷對象是否完成了初始化 |
| weakly_referenced | 對象有或者曾經(jīng)有過ARC的weak對象,如果沒有,析構(gòu)更快 |
| deallocating | 對象正在析構(gòu) |
| has_sidetable_rc | 對象的應(yīng)用計數(shù)太大,無法內(nèi)斂存儲 |
| extra_rc | 這個數(shù)值加1為對象的引用計數(shù).(例如,如果extra_rc為5,則對象的實際引用計數(shù)為6.) |
initIsa
在objc_object的代碼中我們可以看到有好幾初始化方法,最后都會走到下面這個方法,我只是截取了部分我們需要的
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());
if (!nonpointer) {
isa.cls = cls;
} else {
assert(!DisableNonpointerIsa);
assert(!cls->instancesRequireRawIsa());
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;
}
}
有一個地方不好理解
newisa.shiftcls = (uintptr_t)cls >> 3
這里我們可以看到是將類地址右移三位得到有效的33位shiftcls位,Friday Q&A 2013-09-27: ARM64 and You和從 NSObject 的初始化了解 isa 解釋:
使用整個指針大小的內(nèi)存來存儲 isa 指針有些浪費,尤其在 64 位的 CPU 上。在 ARM64 運行的 iOS 只使用了 33 位作為指針(與結(jié)構(gòu)體中的 33 位無關(guān),Mac OS 上為 47 位),而剩下的 31 位用于其它目的。類的指針也同樣根據(jù)字節(jié)對齊了,每一個類指針的地址都能夠被 8 整除,也就是使最后 3 bits 為 0,為 isa 留下 34 位用于性能的優(yōu)化。
絕大多數(shù)機器的架構(gòu)都是 byte-addressable 的,但是對象的內(nèi)存地址必須對齊到字節(jié)的倍數(shù),這樣可以提高代碼運行的性能,在 iPhone5s 中虛擬地址為 33 位,所以用于對齊的最后三位比特為 000,我們只會用其中的 30 位來表示對象的地址
objc_object::ISA()
objc-object.h文件中
inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}
define ISA_MASK 0x0000000ffffffff8ULL
ffffffff8 = 1111 1111 1111 1111 1111 1111 1111 1111 1000共36位,isa.bits和&運算后就拿到33位shiftcls,也就是類指針。
objc_class
Objective-C 中類都是 C 語言的類objc_class。
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() {
return bits.data();
}
}
代碼是不是看著有點奇怪,objc_class繼承自objc_object,說明在Objective-C 中類也是一個對象。我們先來看看objc_class的結(jié)構(gòu):
-
isa:因為
objc_class繼承自objc_object,所以它包含一個isa成員變量,指向(MetaClass)元類 - superclass:指向當(dāng)前類的父類
-
cache:用來緩存指針和
vtable(virtual table虛函數(shù)表)。Runtime運行時系統(tǒng)庫實現(xiàn)了一種自定義的虛函數(shù)表分派機制。這個表是專門用來提高性能和靈活性的。用來存儲IMP類型的數(shù)組 - bits:class_rw_t 指針加上 rr/alloc 標(biāo)志,用來存儲類的屬性,方法,協(xié)議等信息
class_rw_t
class_data_bits_t的data是class_rw_t結(jié)構(gòu)體
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
- methods:方法列表
- properties:屬性列表
- protocols:協(xié)議列表
class_ro_t
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
- instanceSize:instance對象占用的內(nèi)存空間
- name:類名
- baseMethodList:類的原始方法列表
- baseProtocols:類的原始屬性列表
- ivars:成員變量列表
我們看到class_rw_t和class_ro_t都有方法列表等,深入解析 ObjC 中方法的結(jié)構(gòu)對此作出的解釋:
在分析方法在內(nèi)存中的位置時,筆者最開始一直在嘗試尋找只讀結(jié)構(gòu)體 class_ro_t 中的 baseMethods 第一次設(shè)置的位置(了解類的方法是如何被加載的)。嘗試從 methodizeClass 方法一直向上找,直到 _obj_init 方法也沒有找到設(shè)置只讀區(qū)域的 baseMethods 的方法。
而且在 runtime 初始化之后,realizeClass 之前,從 class_data_bits_t 結(jié)構(gòu)體中獲取的 class_rw_t 一直都是錯誤的,這個問題在最開始非常讓我困惑,直到后來在 realizeClass 中發(fā)現(xiàn)原來在這時并不是 class_rw_t 結(jié)構(gòu)體,而是class_ro_t,才明白錯誤的原因。
后來突然想到類的一些方法、屬性和協(xié)議實在編譯期決定的(baseMethods 等成員以及類在內(nèi)存中的位置都是編譯期決定的),才感覺到豁然開朗。
類在內(nèi)存中的位置是在編譯期間決定的,在之后修改代碼,也不會改變內(nèi)存中的位置。
類的方法、屬性以及協(xié)議在編譯期間存放到了“錯誤”的位置,直到 realizeClass 執(zhí)行之后,才放到了 class_rw_t 指向的只讀區(qū)域 class_ro_t,這樣我們即可以在運行時為 class_rw_t 添加方法,也不會影響類的只讀結(jié)構(gòu)。
在 class_ro_t 中的屬性在運行期間就不能改變了,再添加方法時,會修改 class_rw_t 中的 methods 列表,而不是 class_ro_t 中的 baseMethods,對于方法的添加會在之后的文章中分析。
class and metaclass
在前面的源碼中可以看到,對象通過isa找到類,同時類也是一種對象,類隱式的擁有isa,那這個isa指向哪里呢,元類。
static void objc_initializeClassPair_internal(Class superclass, const char *name, Class cls, Class meta)
{
runtimeLock.assertWriting();
class_ro_t *cls_ro_w, *meta_ro_w;
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
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();
// Connect to superclasses and metaclasses
cls->initClassIsa(meta);
if (superclass) {
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;
meta->superclass = cls;
addRootClass(cls);
addSubclass(cls, meta);
}
cls->cache.initializeToEmpty();
meta->cache.initializeToEmpty();
}

上圖實線是 superclass 指針,虛線是isa指針
- 對象調(diào)用實例方法時,是在對應(yīng)類對象及其繼承鏈上找方法。類對象調(diào)用類方法時,是在其元類及繼承鏈上找方法
- 所以元類的isa指針都指向根元類(NSObject),根元類的isa指針指向自己
- 所有的類方法都儲存在元類當(dāng)中
- NSObject 的超類為 nil
關(guān)于元類Classes and metaclasses 和What is a meta-class in Objective-C?解釋的很詳細
下面是What is a meta-class in Objective-C?的測試代碼
Class newClass =
objc_allocateClassPair([NSError class], "RuntimeErrorSubclass", 0);
class_addMethod(newClass, @selector(report), (IMP)ReportFunction, "v@:");
objc_registerClassPair(newClass);
void ReportFunction(id self, SEL _cmd)
{
NSLog(@"This object is %p.", self);
NSLog(@"Class is %@, and super is %@.", [self class], [self superclass]);
Class currentClass = [self class];
for (int i = 1; i < 5; i++)
{
NSLog(@"Following the isa pointer %d times gives %p", i, currentClass);
currentClass = object_getClass(currentClass);
}
NSLog(@"NSObject's class is %p", [NSObject class]);
NSLog(@"NSObject's meta class is %p", object_getClass([NSObject class]));
}
id instanceOfNewClass =
[[newClass alloc] initWithDomain:@"someDomain" code:0 userInfo:nil];
[instanceOfNewClass performSelector:@selector(report)];
[instanceOfNewClass release];
打印結(jié)果:
This object is 0x10010c810.
Class is RuntimeErrorSubclass, and super is NSError.
Following the isa pointer 1 times gives 0x10010c600
Following the isa pointer 2 times gives 0x10010c630
Following the isa pointer 3 times gives 0x7fff71038480
Following the isa pointer 4 times gives 0x7fff71038480
NSObject's class is 0x7fff710384a8
NSObject's meta class is 0x7fff71038480
觀察isa到達過的地址的值:
- 對象的地址是 0x10010c810
- 類的地址是 0x10010c600
- 元類的地址是 0x10010c630
- 根元類(NSObject的元類)的地址是 0x7fff71038480
- NSObject元類的類是它本身
元類是 Class 對象的類。每個類(Class)都有自己獨一無二的元類(每個類都有自己第一無二的方法列表)。這意味著所有的類對象都不同。
元類總是會確保類對象和基類的所有實例和類方法。對于從NSObject繼承下來的類,這意味著所有的NSObject實例和protocol方法在所有的類(和meta-class)中都可以使用。
所有的meta-class使用基類的meta-class作為自己的基類,對于頂層基類的meta-class也是一樣,只是它指向自己而已。
從 NSObject 的初始化了解 isa
Hamster Emporium: [objc explain]: Non-pointer isa - Sealie Software
Testing if an arbitrary pointer is a valid Objective-C object
從 NSObject 的初始化了解 isa
Pro Multithreading and Memory Management for iOS and OS X: with ARC, Grand ...
iOS framework file was built for x86_64 which is not the architecture being linked (arm64), linker command failed with exit code 1
Friday Q&A 2013-09-27: ARM64 and You
深入解析 ObjC 中方法的結(jié)構(gòu)
What is a meta-class in Objective-C?
Classes and metaclasses
用 isa 承載對象的類信息