- 一、OC 轉(zhuǎn) C/C++
- 二馒过、NSObject 對(duì)象內(nèi)存布局
- 三、NSObject 內(nèi)存大小
- 四酗钞、OC 對(duì)象內(nèi)存布局
- 五腹忽、OC 對(duì)象內(nèi)存大小
- 六、對(duì)象分類(lèi)(instance對(duì)象砚作、class對(duì)象窘奏、meta-calss對(duì)象)
- 七斤吐、isa 和 superClass 指針
- 八膨更、對(duì)象屬性、方法數(shù)據(jù)結(jié)構(gòu)
一瑰妄、OC 轉(zhuǎn) C/C++
OC 的底層是通過(guò) C\C++ 實(shí)現(xiàn)米同,所以 OC 代碼編譯過(guò)程一般是先將 OC 轉(zhuǎn)為 C\C++ 骇扇,C\C++ 進(jìn)一步轉(zhuǎn)為匯編語(yǔ)言,最終轉(zhuǎn)為機(jī)器代碼面粮。OC 的對(duì)象映射到 C\C++ 主要對(duì)應(yīng)的是結(jié)構(gòu)體少孝,這里面的 “結(jié)構(gòu)體” 并非 C 語(yǔ)言里面的結(jié)構(gòu)體,而是 C++ 語(yǔ)言里面的結(jié)構(gòu)體熬苍,而且這個(gè)概念僅限字面意思的結(jié)構(gòu)體韭山。嚴(yán)格來(lái)講,其實(shí)C++ 中的struct關(guān)鍵字定義的是類(lèi)冷溃,跟 class 關(guān)鍵字定義的類(lèi)除了默認(rèn)訪問(wèn)權(quán)限的區(qū)別钱磅,沒(méi)有區(qū)別。C++ 中的 struct 對(duì) C 中的 struct 進(jìn)行了擴(kuò)充似枕,它已經(jīng)不再只是一個(gè)包含不同數(shù)據(jù)類(lèi)型的數(shù)據(jù)結(jié)構(gòu)了盖淡,它已經(jīng)獲取了太多的功能。如:能包含成員函數(shù)凿歼、可以繼承褪迟、可以實(shí)現(xiàn)多態(tài)。
通過(guò) xcrun 命令可以將 OC 代碼轉(zhuǎn)為不同平臺(tái)CPU下支持的 C\C++ 代碼答憔,如 OC 代碼轉(zhuǎn)為 arm64 架構(gòu) CPU 代碼味赃,對(duì)應(yīng)的命令為:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件
二、NSObject 對(duì)象內(nèi)存布局
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
return 0;
}
點(diǎn)擊可查看NSObject
定義為如下虐拓,可以看出 NSObject
類(lèi)中包含了一個(gè) isa 成員變量心俗。
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
上述代碼借助 xcrun
命令生成的文件中包含如下代碼,實(shí)際上NSObject
的定義最終也是轉(zhuǎn)為如下代碼蓉驹。
//其中 Class 的定義為:typedef struct objc_class *Class; 64位系統(tǒng)中城榛,指針占據(jù) 8 個(gè)字節(jié)
struct NSObject_IMPL {
Class isa; // 8個(gè)字節(jié)
};
// 可以看出 Class 是一個(gè)指針
// 64位系統(tǒng)中,該指針占據(jù) 8 個(gè)字節(jié)
typedef struct objc_class *Class
NSObject *obj = [[NSObject alloc] init];
的內(nèi)存布局如下态兴。alloc
相當(dāng)于為右側(cè)藍(lán)色的結(jié)構(gòu)體開(kāi)辟一塊空間狠持,結(jié)構(gòu)體中保存著 isa
成員,isa
成員的指針的地址相當(dāng)于結(jié)構(gòu)體地址空間瞻润,初始化成功后喘垂,結(jié)構(gòu)體的地址賦值給 obj
對(duì)象,因此 isa
地址和 obj
地址相同绍撞。
三正勒、對(duì)象內(nèi)存大小
3.1 查看內(nèi)存管大小
//#import <objc/runtime.h>
NSObject *obj = [[NSObject alloc] init];
// 獲得NSObject實(shí)例對(duì)象的成員變量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
// 獲得obj指針?biāo)赶騼?nèi)存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
class_getInstanceSize
方法可以獲取實(shí)例對(duì)象的成員變量大小,即創(chuàng)建一個(gè)實(shí)例對(duì)象楚午,至少需要多少內(nèi)存昭齐。malloc_size
方法可以獲取對(duì)象指針?biāo)赶騼?nèi)存大小,即創(chuàng)建一個(gè)實(shí)例對(duì)象矾柜,實(shí)際上分配了多少內(nèi)存阱驾。兩個(gè)方法的區(qū)別具體可以看runtime
底層源碼觀察其區(qū)別。OC 底層源碼一般可在該網(wǎng)站查看怪蔑。
3.2 class_getInstanceSize 函數(shù)
class_getInstanceSize
底層實(shí)現(xiàn)如下里覆,其中英文注釋很清晰的描述了該方法返回的是成員變量(Class's ivar)大小。
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());
}
3.3 alloc 函數(shù)
OC 中的alloc
在底層調(diào)用 runtime 的allocWithZone
方法:
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;
}
id
class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
static __attribute__((always_inline))
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 {
//該處調(diào)用了C語(yǔ)言的 calloc 函數(shù)開(kāi)辟空間缆瓣,所以查看NSObject 對(duì)象的創(chuàng)建開(kāi)辟空間大小應(yīng)當(dāng)依次為切入點(diǎn)喧枷,查看size 參數(shù)來(lái)源
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;
}
上述代碼調(diào)用了C語(yǔ)言的 calloc
函數(shù)開(kāi)辟空間,所以查看NSObject 對(duì)象的創(chuàng)建開(kāi)辟空間大小應(yīng)當(dāng)依次為切入點(diǎn),查看size
參數(shù)來(lái)源隧甚。alignedInstanceSize()
方法是 class_getInstanceSize
方法底層來(lái)源车荔,所以下面代碼中的extraBytes
變量值實(shí)際為 0。非常值得注意的是戚扳,下述代碼中明確指出CF requires all objects be at least 16 bytes.
忧便,即 CF 對(duì)象至少為 16 位大小。
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
3.4 小結(jié)
綜上帽借,系統(tǒng)分配了 16 個(gè)字節(jié)給 NSObject 對(duì)象(通過(guò) malloc_size 函數(shù)獲得)珠增,但 NSObject 對(duì)象內(nèi)部只使用了 8 個(gè)字節(jié)的空間,這8個(gè)字節(jié)主要用來(lái)存放 isa( 64bit 環(huán)境下砍艾,可以通過(guò) class_getInstanceSize 函數(shù)獲得)蒂教。
四、OC 對(duì)象內(nèi)存布局
4.1 簡(jiǎn)單對(duì)象
@interface Student : NSObject
{
@public
int _no;
int _age;
}
@end
@implementation Student
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc] init];
}
return 0;
}
利用 xcrun 命名生成 C/C++ 代碼脆荷,代碼中會(huì)包含如下部分:
struct Student_IMPL {
Class NSObject_IMP NSObject_IVARS;
int _no;
int _age;
};
其中NSObject_IMP 定義為:
struct NSObject_IMPL {
Class isa;
};
Student_IMP底層進(jìn)而可轉(zhuǎn)為如下結(jié)構(gòu)凝垛,不難看出如果對(duì)象之間存在繼承關(guān)系,最終子類(lèi)轉(zhuǎn)化對(duì)應(yīng)的結(jié)構(gòu)體會(huì)包含父類(lèi)的結(jié)構(gòu)體成分简烘,且父類(lèi)結(jié)構(gòu)體成份在前苔严。
struct Student_IMPL {
Class isa;
int _no;
int _age;
};
為了進(jìn)一步說(shuō)明OC對(duì)象底層是結(jié)構(gòu)體實(shí)現(xiàn)的,可以將OC對(duì)象強(qiáng)制轉(zhuǎn)為結(jié)構(gòu)體孤澎,發(fā)現(xiàn)代碼依然正常運(yùn)行届氢。
Student *stu = [[Student alloc] init];
stu->_no = 4;
stu->_age = 5;
//使用__bridge 將 OC 轉(zhuǎn)為 C
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
NSLog(@"no is %d, age is %d", stuImpl->_no, stuImpl->_age);
結(jié)合上述代碼,可以總結(jié)出Student對(duì)象的底層實(shí)現(xiàn)結(jié)構(gòu)圖:
內(nèi)存布局大概是這樣覆旭,stu 指針指向結(jié)構(gòu)體地址退子,結(jié)構(gòu)體地址即為首個(gè)成員變量的地址。
4.2 繼承對(duì)象
如果是 Person 繼承自 NSObject 型将,Student 繼承自 Person 類(lèi)寂祥,Person 中包含 age 成員變量, Student 包含 no 成員變量七兜,則底層實(shí)現(xiàn)結(jié)構(gòu)如下圖丸凭。
表面上看
Person_IMPL
占據(jù) 8 + 4 = 12 個(gè)字節(jié),但是 OC 中明確指出一個(gè) NSObject 對(duì)象大小至少為 16 字節(jié)腕铸。從內(nèi)存對(duì)齊角度分析來(lái)看,Person_IMPL
結(jié)構(gòu)體也至少為 16 字節(jié)狠裹,即結(jié)構(gòu)體的大小必須是最大成員的倍數(shù)虽界,即8 * 2 = 16。
表面上看 Student_IMPL
占據(jù) 16 + 4 = 20 個(gè)字節(jié)實(shí)際并非如此涛菠,應(yīng)該為 16 字節(jié)莉御。因?yàn)?Student_IMPL
中雖然包含Person_IMPL
撇吞,但是在繼承關(guān)系中,Person_IMPL
中 age 對(duì)應(yīng)的內(nèi)存空間并未被使用礁叔,所以應(yīng)該是 8 + 4 + 4 = 16 字節(jié)牍颈。
五、OC 對(duì)象內(nèi)存大小
@interface Person : NSObject
{
int _age;
int _height;
int _no;
}
@end
Person *p = [[Person alloc] init];
NSLog(@"%zd %zd",class_getInstanceSize([Person class]), malloc_size((__bridge const void *)(p))); // 24 32
p 對(duì)象對(duì)應(yīng)的結(jié)構(gòu)體為:
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _height;
int _no;
}; // 計(jì)算結(jié)構(gòu)體大小晴圾,內(nèi)存對(duì)齊颂砸,24
上述代碼打印結(jié)果分別為 24 和 32,按照前面所說(shuō)的結(jié)構(gòu)體內(nèi)存對(duì)齊原則來(lái)說(shuō)死姚,打印結(jié)果應(yīng)該都為 24,即 8 的最小倍數(shù)勤篮, 8 * 3 = 24都毒。class_getInstanceSize
為結(jié)構(gòu)體內(nèi)存大小 8 + 4 + 4 + 4 = 24,但和實(shí)際情況確有一些出入碰缔。
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
結(jié)合 3.3 小結(jié)的源碼來(lái)分析來(lái)看账劲,class_getInstanceSize
和alloc
方法最終本質(zhì)都是調(diào)用了alignedInstanceSize
函數(shù),其中alloc
調(diào)用alignedInstanceSize
函數(shù)中間使用到了 C 語(yǔ)言的 calloc
函數(shù)金抡,并且還涉及到一個(gè)變量extraBytes
,但是此變量的值一般為 0瀑焦,所以綜合來(lái)看 malloc_size((__bridge const void *)(p))
的值為 32 和calloc
密不可分。因?yàn)樵?code>calloc函數(shù)中存在內(nèi)存對(duì)齊一說(shuō)(注意此內(nèi)存對(duì)齊不等同于結(jié)構(gòu)體內(nèi)存對(duì)齊)梗肝,即在堆區(qū)每次分配內(nèi)存為 16 的倍數(shù)榛瓮。如果比較感興趣,可進(jìn)一步查看 calloc
函數(shù)源碼 libmalloc巫击。
六禀晓、對(duì)象分類(lèi)(instance對(duì)象、class對(duì)象坝锰、meta-calss對(duì)象)
6.1 OC 對(duì)象分為三類(lèi)
- instance 對(duì)象(實(shí)例對(duì)象):內(nèi)部包含成員變量的具體值粹懒、isa 指針
- class 對(duì)象(類(lèi)對(duì)象):內(nèi)部包含對(duì)象方法、屬性顷级、成員變量凫乖、協(xié)議信息、isa 指針弓颈、superclass 指針
- meta-class 對(duì)象(元類(lèi)對(duì)象帽芽,即描述對(duì)象的對(duì)象,元數(shù)據(jù)是指描述數(shù)據(jù)的數(shù)據(jù)):內(nèi)部包含類(lèi)方法恨豁、isa 指針嚣镜、superclass 指針。
無(wú)論是instance橘蜜、class菊匿、meta-class 底層本質(zhì)上都是 OC 對(duì)象付呕。
6.2 類(lèi)對(duì)象和元類(lèi)對(duì)象獲取方式
// 獲取類(lèi)對(duì)象的方式
[NSObject class];
[obj class];
// 獲取元類(lèi)對(duì)象,
// 傳入 instance 返回 class 對(duì)象
// 傳入 class 返回 meta-class 對(duì)象
// 傳入 meta-class 返回 NSObject 的 meta-class 對(duì)象
Class metaClass= object_getClass([NSObject class]);
// 判斷是否為元類(lèi)對(duì)象
class_isMetaClass([NSObject class]);
七跌捆、isa 和 superclass 指針
當(dāng)Student的instance對(duì)象要調(diào)用Person的對(duì)象方法時(shí)徽职,會(huì)先通過(guò)isa找到Student的class,然后通過(guò)superclass找到Person的class佩厚,最后找到對(duì)象方法的實(shí)現(xiàn)進(jìn)行調(diào)用姆钉。
當(dāng)Student的class對(duì)象要調(diào)用Person的class方法時(shí),會(huì)先通過(guò)isa找到Student的meta-class抄瓦,然后通過(guò)superclass找到Person的meta-class潮瓶,最后找到class方法的實(shí)現(xiàn)進(jìn)行調(diào)用。
instance的isa指向class;class的isa指向meta-class;meta-class的isa指向基類(lèi)的meta-class钙姊。
class的superclass指向父類(lèi)的class毯辅,如果沒(méi)有父類(lèi),superclass指針為nil煞额。meta-class的superclass指向父類(lèi)的meta-class思恐,基類(lèi)(Root)的meta-class的superclass指向基類(lèi)的class
(即類(lèi)方法找不到時(shí)會(huì)調(diào)用對(duì)象方向,對(duì)象方法找不到才會(huì)報(bào)錯(cuò))膊毁。
instance調(diào)用對(duì)象方法的軌跡:isa找到class胀莹,方法不存在,就通過(guò)superclass找父類(lèi)婚温。class調(diào)用類(lèi)方法的軌跡:isa找meta-class描焰,方法不存在,就通過(guò)superclass找父類(lèi)缭召。
相關(guān)面試題
為什么 instance栈顷、class、meta-class 對(duì)象中都包含 isa 指針成員變量嵌巷?
因?yàn)樗械?OC 類(lèi)都繼承自 NSObject萄凤。
對(duì)象的isa指針指向聊里?
instance對(duì)象的isa指向class對(duì)象
class對(duì)象的isa指向meta-class對(duì)象
meta-class對(duì)象的isa指向基受的meta- class對(duì)象
0C的類(lèi)信息存故在哪里?
0對(duì)象方法、屬性搪哪、成員變量靡努、協(xié)議信息,存故在class對(duì)象中
0類(lèi)方法,存故在meta-class對(duì)象中
0成員變量的具體值晓折,存故在instance對(duì)象
八惑朦、對(duì)象屬性、方法數(shù)據(jù)結(jié)構(gòu)
無(wú)論是class對(duì)象漓概、meta-class對(duì)象漾月,其底層數(shù)據(jù)結(jié)構(gòu)都和上圖保持一致。
bits & FAST_DATA_MASK 可以獲取到對(duì)應(yīng)的類(lèi)信息胃珍,類(lèi)信息中包含方法列表梁肿、屬性列表蜓陌、協(xié)議列表等信息。