探尋OC對象的本質(zhì)段多,我們平時編寫的Objective-C代碼,底層實現(xiàn)其實都是C\C++代碼壮吩,如圖所示:
OC的對象結(jié)構(gòu)都是通過基礎C\C++的結(jié)構(gòu)體實現(xiàn)的进苍。
我們通過創(chuàng)建OC文件及對象,并將OC文件轉(zhuǎn)化為C++文件來探尋OC對象的本質(zhì)鸭叙。
OC如下代碼
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *objc = [[NSObject alloc] init];
NSLog(@"objc的內(nèi)存地址:%p",&objc);
}
return 0;
}
我們通過命令行將OC的mian.m文件轉(zhuǎn)化為c++文件觉啊。(生成 main.cpp)
clang -rewrite-objc main.m -o main.cpp // 這種方式?jīng)]有指定架構(gòu)例如arm64架構(gòu) 其中cpp代表(c plus plus)
我們可以指定架構(gòu)模式的命令行,使用xcode工具 xcrun沈贝。(生成 main-arm64.cpp )
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
在main-arm64.cpp 文件中搜索NSObjcet杠人,可以找到NSObjcet_IMPL(IMPL代表 implementation 實現(xiàn))。
NSObject_IMPL內(nèi)部的實現(xiàn)
typedef struct objc_class *Class;
struct NSObject_IMPL {
Class isa;
};
// isa 本質(zhì)就是一個指向 objc_class 結(jié)構(gòu)體的指針。
NSObjcet的底層實現(xiàn)嗡善,點擊NSObjcet進入發(fā)現(xiàn)NSObject的內(nèi)部實現(xiàn)辑莫。
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
轉(zhuǎn)化為c語言其實就是一個結(jié)構(gòu)體
struct NSObject_IMPL {
Class isa;
};
一、一個OC對象在內(nèi)存中是如何布局的罩引?
一個OC對象的內(nèi)存布局入下圖所示:
二各吨、一個NSObject對象占用多少內(nèi)存?
既然 isa 本質(zhì)上就是一個指針蜒程,一個指針在32位環(huán)境下占用4個字節(jié)绅你,64位環(huán)境下占用8個字節(jié)伺帘。一個 NSObject 對象結(jié)構(gòu)體內(nèi)部就包含一個 isa 指針昭躺,那么,我們可以認為一個 NSObject 對象就占用8個字節(jié)么伪嫁? NO NO NO领炫,雖然表面如此,但是實際上并不是张咳。那么一個NSObject對象占多大的內(nèi)存空間呢帝洪?
實際上:
1、系統(tǒng)分配了16個字節(jié)給 NSObject 對象(通過 malloc_size 函數(shù)獲得)
2 脚猾、但 NSObject 對象內(nèi)部只使用了8個字節(jié)的空間(64bit環(huán)境下葱峡,可以通過 class_getInstanceSize 函數(shù)獲得)
NSObject *obj = [[NSObject alloc] init];
// 獲得 NSObject 實例對象的成員變量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class])); // 輸出為8
// 獲得 obj 指針所指向內(nèi)存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)(obj))); // 輸出為 16
我們可以通過打斷點。Xcode -> Debug -> Debug Workflow -> View Memory查看:
輸入objc的地址
這個是什么意思呢龙助?其實一個 NSObject 實例對象的大小確實為8個字節(jié)砰奕,但是系統(tǒng)給其分配的內(nèi)存其實是16個字節(jié)。
接下來我們通過objc4源碼來探究下到底是為什么提鸟。
打開下載好的objc4-750 —> objc-class.mm
源碼军援,搜索class_getInstanceSize方法:
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
我們會發(fā)現(xiàn)這個方法返回值是cls->alignedInstanceSize(),點進去查看如下:
// Class's ivar size rounded up to a pointer-size boundary.
// 返回值成員變量的占用內(nèi)存大小
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
我們繼續(xù)看下 malloc_size称勋,由于蘋果部分源碼不公開胸哥,不過不影響今天討論內(nèi)容,我們先 malloc.h 文件中函數(shù)聲明:
/* Returns size of given ptr */
// 注釋意思:返回分配給指針的占用內(nèi)存大小
extern size_t malloc_size(const void *ptr);
總結(jié):通過閱讀源碼赡鲜,發(fā)現(xiàn)一個 NSObject 對象空厌,系統(tǒng)給其分配的空間為 16 個字節(jié),只不過其真正利用起來的只有 8 個字節(jié)银酬。
真的是分配 16 個字節(jié)么蝇庭?
NSObject *obj = [[NSObject alloc] init];
上面這行代碼,可以發(fā)現(xiàn)捡硅,創(chuàng)建一個新的實例對象哮内,分為兩步:
- alloc:分配一塊內(nèi)存空間
- init:初始化
所以,我們想探究實質(zhì)的話可以從 alloc 方法往里面查看,從 alloc 開始搜索的話太多了北发,我們直接從 allocWithZone 開始查看纹因,感興趣的同學可以從 alloc 開始進行查看。
NSObject.mm
+ (id)allocWithZone:(struct _NSZone *)zone {
return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}
allocWithZone 調(diào)用的是: _objc_rootAllocWithZone
id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{
id obj;
#if __OBJC2__
// allocWithZone under __OBJC2__ ignores the zone parameter
(void)zone;
obj = class_createInstance(cls, 0);
#else
if (!zone) {
obj = class_createInstance(cls, 0);
}
else {
obj = class_createInstanceFromZone(cls, 0, zone);
}
#endif
if (slowpath(!obj)) obj = callBadAllocHandler(cls);
return obj;
}
_objc_rootAllocWithZone 分配內(nèi)存空間其實是: class_createInstance
id class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
繼續(xù)點擊進去查看:
id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;
assert(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor);
}
else {
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}
return obj;
}
我們發(fā)現(xiàn)琳拨,最后調(diào)用 C 語言底層的 calloc 分配內(nèi)存函數(shù)瞭恰,我們發(fā)現(xiàn)傳入了一個 size 參數(shù), size 通過 cls 的 instanceSize 函數(shù)獲得狱庇。
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
size_t instanceSize(size_t extraBytes) {
//如果是 NSObject 惊畏,下面這行代碼相當于 size_t size = 8;
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
通過注釋和代碼可以發(fā)現(xiàn),CF:CoreFoundation密任,硬性規(guī)定颜启,返回 size 最小為16。
這是為什么呢浪讳,因為蘋果設計 CF 框架缰盏,包括我們自己設計一套框架,為了我們的框架能夠更好的運行淹遵,肯定會做出一些規(guī)定口猜、約束,這樣就可以理解了透揣。
至于 word_align济炎,涉及到 內(nèi)存對齊 概念,下面的的章節(jié)也會提到一些辐真,但不會涉及太深须尚,感興趣的同學可以 Google 相關文檔。
三拆祈、一個自定義類的對象占用多少內(nèi)存恨闪?
我們可以總結(jié)內(nèi)存對齊為兩個原則:
- 原則 1. 前面的地址必須是后面的地址整數(shù)倍,不是就補齊。
- 原則 2. 整個Struct的地址必須是最大字節(jié)的整數(shù)倍放坏。
講到這里咙咽,相信很多小伙伴還是有很多疑問的。剛才只講了NSObject相關知識淤年。我們平常開發(fā)中肯定不會只用NSObject對象钧敞,基本上都是我們自定義自己的對象,接下來麸粮,來通過兩個復雜一點的例子來進行講解溉苛。
(1)自定義一個 Student 類繼承 NSObject :
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface Student : NSObject{
@public
int _no;
int _age;
}
@end
@implementation Student
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
stu -> _no = 4;
stu -> _age = 5;
NSLog(@"%@",stu); //<student: 0x102974240>
NSLog(@"%zd",class_getInstanceSize([Student class])); //16
}
return 0;
}
@end
按照上述步驟同樣生成c++文件。并查找Student弄诲,我們發(fā)現(xiàn)Student_IMPL 整數(shù)
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _no;
int _age;
};
根據(jù)上文通過 NSObject 實例對象講解的鋪墊愚战,Student 實例對象的本質(zhì)以及其在內(nèi)存中布局如下圖所示:
從圖中最下面把實例對象 stu 強轉(zhuǎn)成結(jié)構(gòu)體類型 stu2娇唯,通過結(jié)構(gòu)體可以正常進行訪問,也從另一角度證明 stu 底層結(jié)構(gòu)確實為 Student_IMPL 結(jié)構(gòu)體類型寂玲。當然也可以從 View Memory 或者 LLDB 進行證明塔插。
內(nèi)存布局這樣畫可能理解更清楚:
我們知道sutdent對象中,包含一個isa指針拓哟,一個int類型的_no成員變量想许,和一個int類型的_age成員變量,同樣isa指針8個字節(jié)断序,_age成員變量4個字節(jié)流纹,_no成員變量4個字節(jié),剛好滿足原則1和原則2违诗,所以student對象占據(jù)的內(nèi)存空間也是16個字節(jié)。
student實際占用內(nèi)存為16字節(jié)挚币,系統(tǒng)分配的內(nèi)存也是16字節(jié)郊楣。
(2)舉一反三燃逻,當 Person 繼承 NSObject,Student 繼承 Person 的情況,一個 Person 對象吼拥,一個 Student 對象占用多少內(nèi)存空間?
Student: Person: NSObject:
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
};
struct Student_IMPL {
struct Person_IMPL Person_IVARS;
int _no;
};
@interface Person: NSObject {
@public
int _age;
}
@end
@implementation Person
@end
@interface Student: Person {
@public
int _no;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
stu->_no = 5;
NSLog(@"student:%zd, %zd", class_getInstanceSize([Student class]), malloc_size((__bridge const void*)stu));
Person *per = [[Person alloc] init];
per->_age = 4;
NSLog(@"person:%zd, %zd", class_getInstanceSize([Person class]), malloc_size((__bridge const void*)per));
}
return 0;
}
我們先來分析下 Person 實例對象占用多少內(nèi)存空間:
struct NSObject_IMPL NSObject_IVARS;即 isa 占用8個字節(jié),int _age; 占4個字節(jié),那么 Person 實例對象占 8 + 4 = 12 個字節(jié)么存璃,錯洒扎,上文中也有提到胡诗,一個 OC 對象至少占用 16 個字節(jié),所以 Person 實例對象占用 16 個字節(jié)匪蝙。
從另外一個角度,其實還有 內(nèi)存對齊 這個概念,就算是沒有 OC對象 至少占用 16 個字節(jié)這個規(guī)定帚称, Person_IMPL 也占用 16 個字節(jié),內(nèi)存對齊有一條規(guī)定:結(jié)構(gòu)體的大小比必須是最大成員大小的倍數(shù)秽澳。
內(nèi)存對齊還有很多規(guī)定肝集,屬于計算機知識范疇,感興趣的同學可以自行 Google仍劈。
我們再來分析下 Student 實例對象占用多少內(nèi)存空間:
- struct Person_IMPL Person_IVARS:占用 16 個字節(jié)寡壮,int _no:占用 4 個字節(jié)这溅,16 + 4 = 20组民,而且剛講了內(nèi)存對齊規(guī)定結(jié)構(gòu)體大小必須是最大成員變量大小的倍數(shù),那么悲靴, Student_IMPL 占用 16 * 2 = 32 個字節(jié)么臭胜?錯!結(jié)果為 16 * 1 = 16 個字節(jié)癞尚。
- 為什么呢庇楞?因為 Person_IMPL 雖然分配16個字節(jié),但是實際變量只占用了 12 個字節(jié)否纬,還有 4 個字節(jié)空出來了吕晌,我們偉大的 iOS 系統(tǒng)會這么傻,白白浪費這 4 個字節(jié)的空間么临燃,當然不會睛驳,**所以,int _no膜廊;其實被放到了 Person_IMPL 空余的 4 個字節(jié)空間當中乏沸。
malloc_size 我們已經(jīng)沒有太多疑問了,但是可能對 class_getInstanceSize 還存在疑問爪瓜,class_getInstanceSize 返回 ivar size蹬跃,即成員變量 size,那么上文 Person instance size 為什么不返回 12 呢铆铆?
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
通過源碼可以發(fā)現(xiàn)蝶缀,class_getInstanceSize 實際返回的其實也是 word_align(unalignedInstanceSize()); 內(nèi)存對齊過的大小。
四薄货、OC對象的分類
Objective-C中的對象翁都,簡稱OC對象,主要可以分為 3 種:
- instance對象(實例對象)
- class對象(類對象)
- meta-class對象(元類對象)
(1) instance對象(實例對象)
instance對象就是通過類alloc出來的對象谅猾,每次調(diào)用alloc都會產(chǎn)生新的instance對象柄慰。
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
object1和object2都是NSObject的instace對象(實例對象),但他們是不同的兩個對象税娜,并且分別占據(jù)著兩塊不同的內(nèi)存坐搔。
instance對象在內(nèi)存中存儲的信息包括:isa指針、其他成員變量敬矩。
(2) class對象(類對象)
我們通過class方法或runtime方法得到一個class對象概行。class對象也就是類對象
// instance 對象,實例對象
NSObject *object = [[NSObject alloc] init];
// class 對象谤绳,類對象
Class objectClass1 = [object class];
Class objectClass2 = [NSObject class];
// class 方法返回的一直是class對象占锯,類對象
// Class objectClass2 = [[[[NSObject class] class] class] class];
Class objectClass3 = object_getClass(object); // runtime
// 0x100444830 0x7fffa5ab9140 0x7fffa5ab9140 0x7fffa5ab9140
NSLog(@"%p %p %p %p", object, objectClass1, objectClass2, objectClass3);
1袒哥、objectClass1~objectClass3都是NSObject的class對象(類對象)。
2消略、每一個類在內(nèi)存中有且只有一個class對象堡称。可以通過打印內(nèi)存地址證明艺演。
3却紧、class對象在內(nèi)存中存儲的信息主要包括:
isa指針、superclass指針胎撤、類的屬性信息(@property)晓殊、類的對象方法信息(instance method)、類的協(xié)議信息(protocol)伤提、類的成員變量信息(ivar巫俺,成員變量的值時存儲在實例對象中的,因為只有當我們創(chuàng)建實例對象的時候才為成員變賦值肿男。但是成員變量叫什么名字介汹,是什么類型,只需要有一份就可以了;所以存儲在class對象中舶沛。)嘹承。
(3)meta-class對象(元類對象)
// class 對象,類對象
Class objectClass = [object class];
// meta-class 對象如庭,元類對象
// 將類對象當做參數(shù)傳入叹卷,獲得元類對象;將實例對象當做參數(shù)傳入,獲得的是類對象
Class metaClass1 = object_getClass([object class]);
Class metaClass2 = object_getClass([NSObject class]);
Class metaClass3 = object_getClass(object_getClass(object));
// 0x7fffa5ab90f0 0x7fffa5ab90f0 0x7fffa5ab90f0 0 1
NSLog(@"%p %p %p %d %d", metaClass1, metaClass2, metaClass3, class_isMetaClass(objectClass), class_isMetaClass(metaClass1));
1坪它、objectMetaClass是NSObject的meta-class對象(元類對象)骤竹。
2、每個類在內(nèi)存中有且只有一個meta-class對象哟楷。
3瘤载、meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的,但是用途不一樣卖擅,在內(nèi)存中存儲的信息主要包括:isa指針、superclass指針墨技、類的類方法的信息(class method)惩阶。
4、meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的扣汪,所以meta-class中也有類的屬性信息断楷,類的對象方法信息等成員變量,但是其中的值可能是空的崭别。
注意:為什么說 meta-class 對象和 class 對象結(jié)構(gòu)一樣冬筒,但是圖上畫的卻不一樣呢恐锣,因為圖上只是將比較重要的一些東西摘了出來,方便理解舞痰。其實本質(zhì)是土榴,class對象類方法信息存儲的可能是null空的,meta-class內(nèi)部屬性信息响牛、對象方法信息玷禽、協(xié)議信息、成員變量信息存儲的可能是null空的呀打。
objc_getClass矢赁、object_getClass方法區(qū)別?
本文和上篇文章有用到這幾個方法贬丛,這幾個方法有什么區(qū)別呢撩银?
Class objc_getClass(const char *aClassName)
{
if (!aClassName) return Nil;
// NO unconnected, YES class handler
return look_up_class(aClassName, NO, YES);
}
Class object_getClass(id obj)
{
// 如果傳入instance對象,返回class對象
// 如果傳入class對象豺憔,返回meta-class對象
// 如果傳入meta-class對象蜒蕾,返回NSObject的meta-class對象
if (obj) return obj->getIsa();
else return Nil;
}
從源碼進行分析,objc_getClass傳入?yún)?shù)為字符串焕阿,根據(jù)字符串去Map中取出類對象并返回咪啡。 object_getClass傳入?yún)?shù)為 id,并且返回值是通過 getIsa 獲得暮屡,說明返回 isa 指向的類型(即:傳入instance對象撤摸,返回類對象;傳入class對象褒纲,返回meta-class對象准夷;傳入meta-class對象,返回NSObject的meta-class對象)莺掠。
五衫嵌、isa指針
我們發(fā)現(xiàn),OC對象不管是instance對象彻秆、類對象還是meta-class都有一個isa指針楔绞,那么,isa指針都指向哪里呢唇兑,起到了什么作用酒朵。
我們都知道,OC對象調(diào)用方法是通過消息機制實現(xiàn)的扎附,通過上面的總結(jié)我們也知道了實例方法存放在class對象中蔫耽,類方法存放在meta-class對象中,那么對象是怎么查找到方法并實現(xiàn)調(diào)用呢留夜?
這個時候就需要isa指針了匙铡,instance對象的isa指針指向class對象图甜,class對象的isa指向meta-class對象,通過isa指針鳖眼,instance對象黑毅、class對象、meta-class對象就可以串起來了具帮,方法調(diào)用博肋、以及各種作用就都可以實現(xiàn)了。
(1)當對象調(diào)用實例方法的時候蜂厅,我們上面講到匪凡,實例方法信息是存儲在class類對象中的,那么要想找到實例方法掘猿,就必須找到class類對象病游,那么此時isa的作用就來了。
[stu studentMethod];
instance對象的isa指向class對象稠通,當調(diào)用對象方法時衬衬,通過instance對象的isa找到class對象,最后找到對象方法的實現(xiàn)進行調(diào)用改橘。
(2)當類對象調(diào)用類方法的時候滋尉,同上,類方法是存儲在meta-class元類對象中的飞主。那么要找到類方法狮惜,就需要找到meta-class元類對象,而class類對象的isa指針就指向元類對象碌识。
[Student studentClassMethod];
class的isa指向meta-class
當調(diào)用類方法時碾篡,通過class的isa找到meta-class,最后找到類方法的實現(xiàn)進行調(diào)用筏餐。
(3)當對象調(diào)用其父類對象方法的時候开泽,又是怎么找到父類對象方法的呢?魁瞪,此時就需要使用到class類對象superclass指針穆律。
[stu personMethod];
[stu init];
當Student的instance對象要調(diào)用Person的對象方法時,會先通過isa找到Student的class佩番,然后通過superclass找到Person的class众旗,最后找到對象方法的實現(xiàn)進行調(diào)用,同樣如果Person發(fā)現(xiàn)自己沒有響應的對象方法趟畏,又會通過Person的superclass指針找到NSObject的class對象,去尋找響應的方法滩租。
(4)當類對象調(diào)用父類的類方法時赋秀,就需要先通過isa指針找到meta-class利朵,然后通過superclass去尋找響應的方法。
[Student personClassMethod];
[Student load];
當Student的class要調(diào)用Person的類方法時猎莲,會先通過isa找到Student的meta-class绍弟,然后通過superclass找到Person的meta-class,最后找到類方法的實現(xiàn)進行調(diào)用著洼。
注意:isa指針并不是直接指向?qū)ο蟮刂分嫡燎玻€需要邏輯與上一個掩碼 ISA_MASK,這個了解下就行身笤,如果不了解的話可以直接理解為isa直接指向class對象豹悬、meta-class對象。
總結(jié)如下:
對isa娇钱、superclass總結(jié):
- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基類的meta-class伤柄,基類的isa指向自己
- class的superclass指向父類的class,如果沒有父類文搂,superclass指針為nil
- meta-class的superclass指向父類的meta-class适刀,基類的meta-class的superclass指向基類的class
- instance調(diào)用對象方法的軌跡,isa找到class煤蹭,方法不存在笔喉,就通過superclass找父類。
- class調(diào)用類方法的軌跡疯兼,isa找meta-class然遏,方法不存在,就通過superclass找父類吧彪。
如何證明isa指針的指向真的如上面所說待侵?
我們通過如下代碼證明:
NSObject *object = [[NSObject alloc] init];
Class objectClass = [NSObject class];
Class objectMetaClass = object_getClass([NSObject class]);
NSLog(@"%p %p %p", object, objectClass, objectMetaClass);
打斷點并通過控制臺打印相應對象的isa指針:我們發(fā)現(xiàn)object->isa與objectClass的地址不同,這是因為從64bit開始姨裸,isa需要進行一次位運算秧倾,才能計算出真實地址。而位運算的值我們可以通過下載objc源代碼找到傀缩。
我們發(fā)現(xiàn),object-isa指針地址0x001dffff96537141經(jīng)過同0x00007ffffffffff8位運算赡艰,得出objectClass的地址0x00007fff96537140
接著我們來驗證class對象的isa指針是否同樣需要位運算計算出meta-class對象的地址售淡。
同時也發(fā)現(xiàn)左邊objectClass對象中并沒有isa指針。我們來到Class內(nèi)部看一下:
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
相信了解過isa指針的同學對objc_class結(jié)構(gòu)體內(nèi)的內(nèi)容很熟悉了揖闸,今天這里不深入研究揍堕,我們只看第一個對象是一個isa指針,為了拿到isa指針的地址汤纸,我們自己創(chuàng)建一個同樣的結(jié)構(gòu)體并通過強制轉(zhuǎn)化拿到isa指針衩茸。
struct xx_cc_objc_class{
Class isa;
};
Class objectClass = [NSObject class];
struct xx_cc_objc_class *objectClass2 = (__bridge struct xx_cc_objc_class *)(objectClass);
此時我們重新驗證一下:
確實,objectClass2的isa指針經(jīng)過位運算之后的地址是meta-class的地址贮泞。