猶記得當(dāng)初學(xué)習(xí)C++的時候,買過一本侯捷老師的《STL源碼剖析》院溺,書里的內(nèi)容基本沒看楣嘁,就記得最前面有句話:
源碼面前,了無秘密
類珍逸、對象逐虚、方法和屬性算是寫OC代碼時接觸的最多的部分了。本篇就以對象為切入點谆膳,分析一下對象和類在runtime層面的表示叭爱。
對象
繼承于NSObject的類所生成的對象在runtime中的表示是這樣的:
struct objc_object {
isa_t isa;
}
很簡單,就一個isa_t結(jié)構(gòu)體漱病,從名字也可以看出來這個結(jié)構(gòu)體指明了這個對象是什么买雾,也就是所屬的類,isa_t結(jié)構(gòu)體的定義如下:
union isa_t {
Class cls;
...
}
(當(dāng)然不止這么點內(nèi)容杨帽,后面會詳細的分析)
可以看到這個結(jié)構(gòu)體中有個類型是Class的屬性cls漓穿,看起來里面應(yīng)該存有關(guān)于這個對象的類的相關(guān)信息,看看Class是如何定義的注盈。
類
typedef struct objc_class *Class;
struct objc_class : 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
}
Class就是結(jié)構(gòu)體objc_class晃危,但是objc_class繼承于objc_object,那就是說類其實也是一個對象老客,只不過比通常我們理解的對象多了一些屬性山害,比如superclass等纠俭。
關(guān)于其他屬性的分析不是本文重點沿量,會在后續(xù)文章中結(jié)合方法(method)的實現(xiàn)進行分析浪慌。
先不看這些屬性,這里還有一個很奇怪的問題朴则,既然類也是一個objc_object权纤,那就是說類也有一個isa指針,那類的isa指針指向哪里呢乌妒?查看了不少資料汹想,這篇講的挺好:classes and metaclasses。
大致的意思是在class之上撤蚊,還有叫做元類(meta class)的存在古掏,而class的isa指針就是指向?qū)?yīng)的meta class。
我們都知道class中存儲的是描述對象的相關(guān)信息侦啸,那么相應(yīng)的meta class中存放的就是描述class相關(guān)信息槽唾。說的更直白一點,在我們寫代碼時光涂,通過對象來調(diào)用的方法(實例方法)都是存儲在class中的庞萍,通過類名來調(diào)用的方法(類方法)都是存儲在meta class中的。
到這里對象和類的關(guān)系已經(jīng)比較清楚了忘闻,但是如果細細思考一下钝计,會發(fā)現(xiàn)還有一個問題,就是meta class也是有isa指針的齐佳,那么這個isa又指向了哪里呢私恬?在上面給出的那篇文章里面有這么一張圖:
這張圖解釋的非常清楚,meta class的isa指向了root meta class(絕大部分情況下root class就是NSObject)炼吴,root meta class的isa指向自身本鸣,isa的鏈路就是這樣了。
isa_t結(jié)構(gòu)體分析
先看看isa_t的完全版缺厉,因為運行環(huán)境是osx永高,所以只截取x86_64部分,arm64的區(qū)別只在于部分字段的位數(shù)不同提针,字段是完全相同的:
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 44;
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 8;
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
};
}
看這個定義只能大概看出個框架命爬,下面從isa的初始化過程來看看isa_t究竟是如何存儲類或者元類的相關(guān)信息。
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
initIsa(cls, true, hasCxxDtor);
}
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;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
}
上來就看不懂辐脖,nonpointer是個什么饲宛,為什么在這里傳的是true?在之前那位大神的另一篇文章中也有解釋:Non-pointer isa嗜价。
大概的意思是在64位系統(tǒng)中艇抠,為了降低內(nèi)存使用幕庐,提升性能,isa中有一部分字段用來存儲其他信息家淤。這也解釋了上面isa_t的那部分結(jié)構(gòu)體异剥。
這有點像taggedPointer,兩者之間有什么區(qū)別絮重?備注一下后面再研究冤寿。
現(xiàn)在知道了nonpointer為什么是true,那么把initIsa方法先簡化一下:
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
}
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
一共三部分:
- newisa.bits = ISA_MAGIC_VALUE;
從ISA_MAGIC_VALUE的定義中可以看到這個字段初始化了兩個部分青伤,一個是magic字段(6位:111011)督怜,一個是nonpointer字段(1位:1),magic字段用于校驗狠角,nonpointer之前已經(jīng)詳細分析過了号杠。 - newisa.has_cxx_dtor = hasCxxDtor;
這個字段存儲類是否有c++析構(gòu)器。 - newisa.shiftcls = (uintptr_t)cls >> 3;
將cls右移3位存到shiftcls中丰歌,從isa_t的結(jié)構(gòu)體中也可以看到低3位都是用來存儲其他信息的姨蟋,既然可以右移三位,那就代表類地址的低三位全部都是0动遭,否則就出錯了芬探,補0的作用應(yīng)該是為了字節(jié)對齊。
因為nonpointer的緣故厘惦,isa并不只是用來存儲類地址了偷仿,所以需要提供一個額外的方法來返回真正的地址:
inline Class
objc_object::ISA()
{
return (Class)(isa.bits & ISA_MASK);
}
# define ISA_MASK 0x00007ffffffffff8ULL
其實就是取isa_t結(jié)構(gòu)體的shiftcls字段。
其他字段
還有一些其他的字段宵蕉,把上面那篇文章中相關(guān)部分翻譯過來放在下面:
// 是否曾經(jīng)或正在被關(guān)聯(lián)引用酝静,如果沒有,可以快速釋放內(nèi)存
uintptr_t has_assoc : 1;
// 對象是否曾經(jīng)或正在被弱引用羡玛,如果沒有别智,可以快速釋放內(nèi)存
uintptr_t weakly_referenced : 1;
// 對象是否正在釋放內(nèi)存
uintptr_t deallocating : 1;
// 對象的引用計數(shù)太大,無法存儲
uintptr_t has_sidetable_rc : 1;
// 對象的引用計數(shù)超過1稼稿,比如10薄榛,則此值為9
uintptr_t extra_rc : 8;
例子
下面通過代碼驗證一下之前關(guān)于isa的鏈路,先創(chuàng)建一個用于測試TestObject類让歼,相關(guān)代碼如下:
// TestObject.h
#import <Foundation/Foundation.h>
@interface TestObject : NSObject
@end
// TestObject.m
#import "TestObject.h"
@implementation TestObject
@end
為了方便打條件斷點敞恋,先通過log獲取TestObject在內(nèi)存中的位置:0x100001180,這個時候main函數(shù)是這個樣子的:
int main(int argc, const char * argv[]) {
@autoreleasepool {
TestObject *testObj = [TestObject new];
NSLog(@"%p", [testObj class]);
NSLog(@"%p", [TestObject class]);
NSLog(@"%p", [NSObject class]);
}
return 0;
}
只要代碼不變谋右,這個類在內(nèi)存中的地址就不會變
所以在initIsa()方法中添加一個條件斷點硬猫,并重新運行:
運行程序,當(dāng)進入斷點的時候可以看到方法的調(diào)用棧是這樣的:
找到2 _class_createInstanceFromZone(),在方法最后打個斷點啸蜜,繼續(xù)運行程序進入此斷點坑雅,輸出obj的內(nèi)存地址:
Printing description of obj:
<TestObject: 0x101301090>
接下來通過這個地址來測試一下isa的鏈路:
//方法中的obj類型是id,id就是objc_object*衬横,所以強轉(zhuǎn)一下
(lldb) p (objc_object *)0x101301090
(objc_object *) $3 = 0x0000000101301090
(lldb) p $3->isa
(objc_class *) $4 = 0x001d800100001181 // 對象的isa
(lldb) p (objc_object *)0x100001180 // 根據(jù)上面isa_t結(jié)構(gòu)體裹粤,找到shiftcls的地址,也就是類的真實地址
(objc_object *) $5 = 0x0000000100001180 // TestObject類的真實地址冕香,可以看到與之前打印的[TestObject class]地址是相同的
(lldb) p $5->isa
(objc_class *) $6 = 0x001d800100001159 // 類的isa
(lldb) p (objc_object *)0x100001158
(objc_object *) $7 = 0x0000000100001158 // TestObject元類的真實地址
(lldb) p $7->isa
(objc_class *) $8 = 0x001d8001004a0e49 // 根元類的isa
(lldb) p (objc_object *)0x1004a0e48
(objc_object *) $9 = 0x00000001004a0e48
(lldb) p $9->isa
(objc_class *) $10 = 0x001d8001004a0e49 // 可以看到根元類的isa確實指向自身
(lldb)
測試結(jié)果與圖class diagram.jpeg給出的完全相同蛹尝。
在main函數(shù)的return行添加斷點,運行程序進入斷點悉尾,有如下輸出:
NSLog(@"%p", [testObj class]);
NSLog(@"%p", [TestObject class]);
NSLog(@"%p", [NSObject class]);
log輸出:
0x100001180
0x100001180
0x1004a0e98
前兩個log結(jié)果相同,稍后再分析挫酿,這里先看一下NSObject的元類isa:
(lldb) p (objc_object *)0x1004a0e98
(objc_object *) $11 = 0x00000001004a0e98
(lldb) p $11->isa
(objc_class *) $12 = 0x001d8001004a0e49
可以看到此處$12的值與上方$10是完全相同的构眯,也就驗證了NSObject的meta class就是一般類的root meta class。
下面再來看看上面那兩個相同的輸出早龟,也就是
TestObject *testObj = [TestObject new];
NSLog(@"%d", [testObj class] == [TestObject class]);
這個log會輸出1
看起來有點奇怪惫霸,但是只要看一下源代碼實現(xiàn)就能理解了。
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
object_getClass方法最終返回的是isa葱弟。所以TestObject調(diào)用class方法壹店,返回的是自身;testObj調(diào)用class方法芝加,返回的是isa指向的類硅卢,也是TestObject。所以上面結(jié)果相同就不奇怪了藏杖。
看到這里就順便看一下可能會接觸到isa的常用的幾個方法:isMemberOfClass将塑,isKindOfClass。廢話不說蝌麸,直接上源碼:
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
從源碼來看這兩個方法就一目了然了点寥。有興趣的可以寫幾個例子測試一下。