oc的本質(zhì)、底層結(jié)構(gòu)、內(nèi)存分析、isa指針和superclass指針分析

1、在開始前先說下怎么將oc代碼轉(zhuǎn)為c++代碼

方法1
1、打開終端cd到目標(biāo)的工程文件
2、終端輸入:clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxx.m,其中xxx.m替換成自己需要轉(zhuǎn)換的文件,然后敲回車

方法2
1、打開終端cd到目標(biāo)的工程文件
2、xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o xxx.cpp,將xxx改為自己需要轉(zhuǎn)換的文件名才回車就可以了
如果需要鏈接其他框架,使用-framework參數(shù)。比如-framework UIKit

在終端上執(zhí)行了上面兩個(gè)方法中任何一個(gè)后,回到工程文件中就可以看到多了一個(gè)cpp文件,將cpp文件拖拽到工程中就可以在xcode里看到了

注意
在將cpp文件添加到工程中后最好將cpp文件從編譯器中移除,否則在編譯的時(shí)候會報(bào)錯(cuò)。

1-1.png

oc轉(zhuǎn)c\c++詳細(xì)流程可以看這里iOS將oc的.m文件編譯成C++的.cpp文件

2、oc的底層實(shí)現(xiàn)

  • oc的底層實(shí)現(xiàn)都是c\c++代碼,oc的面向?qū)ο蠖际腔赾\c++的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的
  • oc的對象和類主要是基于c\c++的結(jié)構(gòu)體實(shí)現(xiàn)的
  • 編譯器會先將oc代碼轉(zhuǎn)化為c\c++代碼,再將c\c++代碼轉(zhuǎn)化為匯編語言,然后再轉(zhuǎn)化為機(jī)器語言


    1-2.png

下面我們創(chuàng)建一個(gè)NSObject對象,然后再轉(zhuǎn)化成c++代碼,看下在c++中是什么樣的結(jié)構(gòu)

NSObject * object = [[NSObject alloc] init];

在oc中NSObject的定義是這樣的

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

在c++中是一個(gè)結(jié)構(gòu)體

struct NSObject_IMPL {
    Class isa;
};

由此可以證明上面的結(jié)論oc的對象和類主要是基于c\c++的結(jié)構(gòu)體實(shí)現(xiàn)的

class又是什么呢?clas是一個(gè)指針

typedef struct objc_class *Class;

如果創(chuàng)建一個(gè)Person類繼承自NSObject其底層又是怎樣實(shí)現(xiàn)的呢?

Person * person = [[Person alloc] init];

轉(zhuǎn)化為c++后

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
};

NSObject_IMPL就是NSObject的底層

struct NSObject_IMPL {
    Class isa;
};

那么可以在Person_IMPL中將NSObject_IMPL看成是isa指針,那么就等價(jià)于下面的寫法

struct Person_IMPL {
    Class isa;
};

如果Person帶有成員變量呢?

@interface Person : NSObject
{
    int _age;
    NSString * _name;
}

其底層為

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    NSString *_name;
};

如果再創(chuàng)建一個(gè)Student類繼承自Person并帶有了height成員變量呢?

@interface Student : Person
{
    int _height;
}

轉(zhuǎn)化為c++后

struct Student_IMPL {
    struct Person_IMPL Person_IVARS;
    int _height;
};
  • 由上面的Person和Student轉(zhuǎn)化為c++后可以看出,子類中包含了父類的結(jié)構(gòu)體
  • 每個(gè)對象都包含一個(gè)isa指針

3、oc對象內(nèi)存詳解

下面先用兩個(gè)方法去打印NSObject對象的內(nèi)存大小

NSObject * object = [[NSObject alloc] init];
        
NSLog(@"%zd",class_getInstanceSize([NSObject class]));
NSLog(@"%zd",malloc_size((__bridge const void *)object));

這兩個(gè)方法分別打印的是8和16,為什么這兩個(gè)方法打印出來的內(nèi)存大小不一樣呢。
class_getInstanceSize是獲取一個(gè)實(shí)例對象創(chuàng)建至少需要多少內(nèi)存
malloc_size是獲取創(chuàng)建一個(gè)實(shí)例對象,實(shí)際上分配了多少內(nèi)存
為什么會有一個(gè)最少內(nèi)存和一個(gè)分配內(nèi)存呢?因?yàn)閛c中有一個(gè)內(nèi)存對齊規(guī)則。
內(nèi)存對齊:簡單的理解就是最終的內(nèi)存大小為成員中內(nèi)存最大的整數(shù)倍,不足的要對齊。

在64位的環(huán)境下oc中對象存取是以8字節(jié)來計(jì)算的,對象開辟空間的內(nèi)存是以16字節(jié)來對齊的。

想要詳細(xì)了解iOS中內(nèi)存對齊的可以看下面兩位大神的文章,建議先看第一篇文章再看第二篇。
這篇通俗易懂的講解了iOS中的內(nèi)存對齊的應(yīng)用
這篇很好的講了內(nèi)存對齊的定義
先看了第一篇才能很好的理解第二篇文章中內(nèi)存對齊的定義

怎么計(jì)算出NSObject最少內(nèi)存為8,分配內(nèi)存為16呢?

因?yàn)镹SObject在c++中實(shí)際為一個(gè)結(jié)構(gòu)體

struct NSObject_IMPL {
    Class isa;
};

NSObject_IMPL結(jié)構(gòu)體中包含了一個(gè)isa指針,指針在64位的環(huán)境下為8個(gè)字節(jié)。結(jié)構(gòu)體的內(nèi)存大小所其成員所決定,又因?yàn)?code>oc中對象存取是以8字節(jié)來對齊的,NSObject_IMPL結(jié)構(gòu)體成員大小為8,剛好為8的整數(shù)倍不需要補(bǔ)齊,所以NSObject最少內(nèi)存為8。因?yàn)?code>oc對象開辟空間是以16字節(jié)對齊的,NSObject的內(nèi)存為8,不是16的整數(shù)倍,需要補(bǔ)齊是內(nèi)存為16的整數(shù)倍,所以補(bǔ)齊后內(nèi)存就為16了。

Person繼承自NSObject,并有兩個(gè)成員變量,那么Person的最少內(nèi)存和實(shí)際分配內(nèi)存分別是多少呢?

@interface Person : NSObject
{
    int _age;
    NSString * _name;
}

將Person轉(zhuǎn)化為c++后

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    NSString *_name;
};

(本文章所說的內(nèi)存都是在64位環(huán)境下的)NSObject_IMPL里是一個(gè)isa指針,為8字節(jié),_age為4字節(jié),_name為8字節(jié)。8+4+8=20,又因?yàn)?code>oc中對象存取是以8字節(jié)來對齊的所以Person最少內(nèi)存為24。因?yàn)?code>oc對象開辟空間是以16字節(jié)對齊的所以實(shí)際分配內(nèi)存為32。

需要注意的是蘋果為了節(jié)省內(nèi)存空間對內(nèi)存做了重排,所以在分配內(nèi)存時(shí),并不是按你的成員變量書寫順序去分配的

4、oc對象的分類

oc中的對象主要分為3類分別為:instance對象(實(shí)例對象)、class對象(類對象)、meta-class對象(元類對象)。

1、instance對象(實(shí)例對象)
通過alloc出來的對象就是實(shí)例對象,每次alloc都會生成一個(gè)實(shí)例對象。

        NSObject * obj1 = [[NSObject alloc] init];
        NSObject * obj2 = [[NSObject alloc] init];
        
        NSLog(@"obj1:%p obj2:%p",obj1,obj2);

上面obj1和obj2就是兩個(gè)不同實(shí)例對象,打印出內(nèi)存地址分別是

obj1:0x10070a740 obj2:0x10070a750

內(nèi)存地址不同就說明了是兩個(gè)不同的對象,分別占據(jù)著兩塊不同的內(nèi)存。
在上面對象本質(zhì)的一部分,我們看到了實(shí)例對象在內(nèi)存中存儲了isa指針和成員變量。

2、class對象(類對象)
每個(gè)類在內(nèi)存中有且只有一個(gè)class對象

        NSObject * obj1 = [[NSObject alloc] init];
        NSObject * obj2 = [[NSObject alloc] init];
        
        Class objClass1 = [obj1 class];
        Class objClass2 = [obj2 class];
        
        NSLog(@"objClass1:%p    objClass2:%p",objClass1,objClass2);

objClass1和objClass2都是類對象,打印出來的結(jié)果為

objClass1:0x7fff80670388    objClass2:0x7fff80670388

打印出來的地址是一樣的,是同一塊內(nèi)存,所以是同一個(gè)class對象。
class對象在內(nèi)存中存儲的信息主要包括:isa指針、類的屬性信息(@property)、類的對象方法信息(instance method)、類的協(xié)議信息(protocol)、類的成員變量(ivar)等。

3、meta-class對象(元類對象)
每個(gè)類在內(nèi)存中有且只有一個(gè)meta-class對象,meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的,但是用途不一樣,在內(nèi)存中存儲的信息主要包括isa指針、superclass指針、類的類方法信息(class method)

獲取元類對象需要用到runtime

Class metaClass = object_getClass([NSObject class]);

需要注意的是通過下面方法獲取到的不是元類對象,而是類對象

Class objClass = [[NSObject class] class];

判斷一個(gè)對象是否為元類對象,可以通過下面的方法(也是runtime中的方法)

BOOL result = class_isMetaClass([NSObject class]);

5、isa指針

由上面的知識點(diǎn),我們已經(jīng)知道在實(shí)例對象、類對象和元類對象中都有著一個(gè)isa指針,isa指針有什么用呢?我們先看下下面1-3這張圖

1-3.png

  • instanceisa指向class
    當(dāng)調(diào)用對象方法時(shí),因?yàn)閷ο蠓椒ㄊ欠旁?code>class對象中的,所以instance對象會通過自己的isa指針找到class,最后找到對象方法的實(shí)現(xiàn)進(jìn)行調(diào)用。

  • classisa指向meta-class
    當(dāng)調(diào)用類方法時(shí),因?yàn)轭惙椒ㄊ欠旁谠獙ο笾械?,所以類對象會先通過本身的isa指針找到meta-class,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用。

  • meta-classisa指向基類的meta-calss
    這里需要注意的是meta-classisa指向基類的meta-calss,而不是父類的meta-calss。
    例如有A、B、C、D四個(gè)類,A是基類,D繼承C,C繼承B,B繼承A,那A、B、C、D四個(gè)類的meta-classisa分別指向誰呢?
    答案是都指向A的meta-class,因?yàn)?code>meta-class的isa指向基類的meta-class,A是B、C、D的基類,因?yàn)锳本身就是基類,所以A的meta-calssisa指向自己的meta-class。

  • 從64bit開始,isa需要進(jìn)行一次位運(yùn)算,才能計(jì)算出真實(shí)地址

    1-4.png

6、superclass指針

  • superclass指針指向父類,classsuperclass指向class的父類,meta-classsuperclass指向meta-class的父類
  • classsuperclass指向父類的class,如果沒有父類,superclass制作為nil
  • meta-classsuperclass指向父類的meta-class,基類的meta-classsuperclass指向基類的class
  • instance調(diào)用對象方法的軌跡:isa找到class,方法不存在,就通過superclass找父類,從父類的方法列表中找
  • class調(diào)用類方法的軌跡:isameta-class,方法不存在,就通過superclass找父類,從父類的方法列表中找

從1-4圖中可以看到,instance中沒有superclass指針,classmeta-class中都有superclass指針。
為什么instance中沒有superclass制作呢,因?yàn)?code>instance中已經(jīng)包含了父類的成員變量,所以根本不需要superclass指針再去指向父類獲取成員變量了。例如創(chuàng)建一個(gè)Student類繼承自Person,那么Student的實(shí)例對象底層就是這樣的。

struct Student_IMPL {
    struct Person_IMPL Person_IVARS;
    int _height;
};

對這結(jié)構(gòu)不是很明白的可以滑到文章上面再看下oc底層實(shí)現(xiàn)這一塊的知識點(diǎn)。

class對象的superclass指針
Person是Student的父類
當(dāng)Student的instance對象要調(diào)用Person的對象方法時(shí),會先通過isa找到Student的class,然后通過superclass找到Person的class,最后找到對象方法的實(shí)現(xiàn)進(jìn)行調(diào)用

meta-class對象的superclass指針
Person是Student的父類
當(dāng)Student的class要調(diào)用Person的類方法時(shí),會先通過isa找到Student的meta-class,然后通過superclass找到Person的meta-class,最后找到類方法的實(shí)現(xiàn)進(jìn)行調(diào)用

1-5是一張非常經(jīng)典的圖大家可以看下


1-5.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容