iOS底層原理總結 - 探尋OC對象的本質

iOS底層原理總結 - 探尋OC對象的本質

對小碼哥底層班視頻學習的總結與記錄。

面試題:一個NSObject對象占用多少內(nèi)存祝峻?

  • 探尋OC對象的本質,我們平時編寫的Objective-C代碼洒缀,底層實現(xiàn)其實都是C\C++代碼序调。


    OC代碼的轉化過程.png
  • 所以OC的面向對象都是基于C\C++的數(shù)據(jù)結構實現(xiàn)的。
  • 思考:OC的對象侈离、類主要是基于C\C++的什么數(shù)據(jù)結構實現(xiàn)的峦筒?
    • 結構體
  • 我們通過創(chuàng)建OC文件及對象,并將OC文件轉化為C++文件來探尋OC對象的本質锄列,將OC代碼轉換成C\C++代碼
  • OC如下代碼
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [[NSObject alloc] init];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}
  • 我們通過命令行將OC的mian.m文件轉化為c++文件图云。
clang -rewrite-objc main.m -o main.cpp // 這種方式?jīng)]有指定架構例如arm64架構 其中cpp代表(c plus plus)
生成 main.cpp
  • 我們可以指定架構模式的命令行,使用xcode工具 xcrun
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 
//生成 main-arm64.cpp
//xcrun是xcode的一個工具邻邮,xc是xcode的簡稱
//iphoneos是指定平臺竣况,上面一句是在iphone上面的
//-arch 后面是架構,例如:模擬器(i386)饶囚,32bit(armv7)帕翻,64bit(arm63)
//  OC源文件  -o  輸出的CPP文件
  • main-arm64.cpp 文件中搜索NSObjcet,可以找到NSObjcet_IMPL(IMPL代表 implementation 實現(xiàn))
  • 我們看一下NSObject_IMPL內(nèi)部
struct NSObject_IMPL {
    Class isa;
};
// 查看Class本質萝风,command點擊進入class
typedef struct objc_class *Class;
我們發(fā)現(xiàn)Class其實就是一個指針嘀掸,對象底層實現(xiàn)其實就是這個樣子。
  • 補充:查看 objc_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;

  • 有時候我們在看底層實現(xiàn)的時候會直接commen點擊進入查看;
  • 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
}
@end

  • NSObjcet的定義勋陪,簡化一下就是:
@interface NSObject {
    Class isa ;
}
@end
  • 最終轉化為c語言其實就是一個結構體
struct NSObject_IMPL {
    Class isa;
};
  • 思考: 一個OC對象在內(nèi)存中是如何布局的。


    NSObject的底層實現(xiàn) .png
  • 那么這個結構體占多大的內(nèi)存空間呢硫兰,我們發(fā)現(xiàn)這個結構體只有一個成員诅愚,isa指針,而指針在64位架構中占用8個字節(jié)劫映。也就是說一個NSObjec對象所占用的內(nèi)存是8個字節(jié)违孝。(這個地方說法是不準確的,準確的說是泳赋,一個NSObjec實例對象的成員變量所占用的內(nèi)存是8個字節(jié))雌桑;

  • 但是我們發(fā)現(xiàn)NSObject對象中還有很多方法,那這些方法不占用內(nèi)存空間嗎祖今?其實類的方法等也占用內(nèi)存空間校坑,但是這些方法所占用的存儲空間并不在NSObject對象中。

  • 為了探尋OC對象在內(nèi)存中如何體現(xiàn)千诬,我們來看下面一段代碼

NSObject *objc = [[NSObject alloc] init];
  • 上面一段代碼在內(nèi)存中如何體現(xiàn)的呢耍目?上述一段代碼中系統(tǒng)為NSObject對象分配8個字節(jié)的內(nèi)存空間,用來存放一個成員isa指針大渤。那么isa指針這個變量的地址就是結構體的地址制妄,也就是NSObjcet對象的地址。
  • 假設isa的地址為0x100400110泵三,那么上述代碼分配存儲空間給NSObject對象耕捞,然后將存儲空間的地址賦值給objc指針。objc存儲的就是isa的地址烫幕。objc指向內(nèi)存中NSObject對象地址俺抽,即指向內(nèi)存中的結構體,也就是isa的位置较曼。
  • 到這里我們回想面試題磷斧,一個NSObject對象占用多少內(nèi)存?捷犹,按照我們的分析弛饭,應該是占用了8個字節(jié),但是萍歉,這道題的答案不是8個字節(jié)侣颂,是16個字節(jié);分析如下:
  • 導入runtime的頭文件枪孩,(#import <objc/runtime.h>)里面有一個函數(shù)憔晒,class_getInstanceSize()藻肄;
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
//這個函數(shù)的解釋是返回一個類的實例對象的大小
//獲得NSObject實例對象的成員變量所占用的大小 >> 8
  • 導入malloc頭文件,(#import <malloc/malloc.h>)里面有一個函數(shù)拒担,malloc_size()嘹屯;
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
//獲得obj指針所指向內(nèi)存的大小 >> 16
  • 此時可以看到,第一個函數(shù)打印的是8从撼,第二個函數(shù)打印的是16州弟;那為什么第一個函數(shù)打印的是8,第二個函數(shù)打印的是16呢低零?
  • 輸入opensource.apple.com/tarballs查看蘋果的源碼呆馁;
  • 搜索objc4,下載毁兆,打開,
  • 搜索class_getInstanceSize阴挣,查看具體的實現(xiàn)方法
size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}
  • 我們發(fā)現(xiàn)內(nèi)部調(diào)用了 alignedInstanceSize()這個函數(shù)气堕,command點擊進入發(fā)現(xiàn),我們發(fā)現(xiàn)這個函數(shù)的描述是返回這個類的成員變量所占用的大小
// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }

  • alloc的本質調(diào)用的是allocWithZone畔咧,搜索allocWithZone茎芭,發(fā)現(xiàn)最終調(diào)用的是rootAllocWithZone,找到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;
}
  • command點擊進入class_createInstance()函數(shù)
id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}
  • command進入_class_createInstanceFromZone()函數(shù)
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)calloc()函數(shù)來分配內(nèi)存誓沸,分配的size梅桩,來自instanceSize()函數(shù),command進入instanceSize()函數(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;
    }
    
  • 此時會發(fā)現(xiàn)當size小于16的時候拜隧,就把size設置為16宿百,所以,到現(xiàn)在為止洪添,這道面試題的答案是16垦页;

回答面試題:

  • 一個NSObject對象占用多少內(nèi)存?
    • 系統(tǒng)分配了16個字節(jié)給NSObject對象(通過malloc_size函數(shù)獲得)
    • 但NSObject對象內(nèi)部只使用了8個字節(jié)的空間(64bit環(huán)境下干奢,可以通過class_getInstanceSize函數(shù)獲得)

自定義類的實例對象內(nèi)存分配情況

面試題:在64bit環(huán)境下痊焊,自定類的實例對象占用多少內(nèi)存呢?

  • 首先創(chuàng)建一個Student類
@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);
    }
    return 0;
}
@end
  • 我們按照上面的OC代碼轉C++文件的方式進行轉換忿峻。我們從 main-arm64.cpp 文件中搜索 Student,并查找Student薄啥,我們發(fā)現(xiàn)Student_IMPL:
struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
    int _age;
};
  • 我們發(fā)現(xiàn)Student類轉化為C++的結構體后第一項是struct NSObject_IMPL ,而通過上面的實驗我們知道NSObject_IMPL內(nèi)部其實就是Class isa
struct NSObject_IMPL {
    Class isa;
};
  • 那么我們假設 struct NSObject_IMPL NSObject_IVARS; 等價于 Class isa;可以將上述代碼轉化為
struct Student_IMPL {
    Class *isa;
    int _no;
    int _age;
};
  • 因此此結構體占用多少存儲空間逛尚,對象就占用多少存儲空間垄惧。遵循上面計算NSObject對象內(nèi)存的方式,結構體內(nèi)的各個成員變量占用內(nèi)存總和就是結構體占用總的內(nèi)存大小黑低,isa指針8個字節(jié)空間+int類型_no4個字節(jié)空間+int類型_age4個字節(jié)空間共16個字節(jié)空間赘艳;
Student *stu = [[Student alloc] init];
stu -> _no = 4;
stu -> _age = 5;
  • 那么上述代碼實際上在內(nèi)存中的體現(xiàn)為酌毡,創(chuàng)建Student對象首先會分配16個字節(jié),存儲3個東西蕾管,isa指針8個字節(jié)枷踏,4個字節(jié)的_no ,4個字節(jié)的_age


    Student對象的存儲空間.png
  • sutdent對象的3個變量分別有自己的地址。而stu指向isa指針的地址掰曾。因此stu的地址為0x100400110旭蠕,stu對象在內(nèi)存中占用16個字節(jié)的空間。并且經(jīng)過賦值旷坦,_no里面存儲4 掏熬,_age里面存儲5
  • 驗證Student在內(nèi)存中模樣
struct Student_IMPL {
    Class isa;
    int _no;
    int _age;
};

@interface Student : NSObject
{
    @public
    int _no;
    int _age;
}
@end

@implementation Student

int main(int argc, const char * argv[]) {
    @autoreleasepool {
            // 強制轉化
            struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
            NSLog(@"_no = %d, _age = %d", stuImpl->_no, stuImpl->_age); // 打印出 _no = 4, _age = 5
    }
    return 0;
}
  • 上述代碼將oc對象強轉成Student_IMPL的結構體。也就是說把一個指向oc對象的指針秒梅,指向這種結構體旗芬。由于我們之前猜想,對象在內(nèi)存中的布局與結構體在內(nèi)存中的布局相同捆蜀,那么如果可以轉化成功疮丛,說明我們的猜想正確。由此說明stu這個對象指向的內(nèi)存確實是一個結構體辆它。
  • 上面的方法是根據(jù)類型推算出來的內(nèi)存大小誊薄,我們還可以根據(jù)代碼計算出來,即運行時方法來獲取锰茉。
NSLog(@"NSObject = %zd",class_getInstanceSize([NSObject class]));
//類對象實際需要內(nèi)存大小
 NSLog(@"Student = %zd", class_getInstanceSize([Student class]));
//系統(tǒng)分配
 NSLog(@"Student = %zd", malloc_size((__bridge const void *)stu));
OC對象本身占用內(nèi)存大小.png

窺探內(nèi)存結構

實時查看內(nèi)存數(shù)據(jù)

  • 方式一:通過打斷點呢蔫。
    • Debug Workflow -> viewMemory address中輸入stu的地址,類似查看NSObject對象的地址


      查看內(nèi)存地址方式.png

      查看結果.png
  • 從上圖中飒筑,我們可以發(fā)現(xiàn)讀取數(shù)據(jù)從高位數(shù)據(jù)開始讀片吊,查看前16位字節(jié),每四個字節(jié)讀出的數(shù)據(jù)為16進制 0x0000004(4字節(jié)) 0x0000005(4字節(jié)) isa的地址為 00D1081000001119(8字節(jié))
方式二:通過lldb指令xcode自帶的調(diào)試器
memory read 0x10074c450
// 簡寫  x 0x10074c450

// 增加讀取條件
// memory read/數(shù)量格式字節(jié)數(shù)  內(nèi)存地址
// 簡寫 x/數(shù)量格式字節(jié)數(shù)  內(nèi)存地址
// 格式 x是16進制协屡,f是浮點定鸟,d是10進制
// 字節(jié)大小   b:byte 1字節(jié),h:half word 2字節(jié)著瓶,w:word 4字節(jié)联予,g:giant word 8字節(jié)

示例:x/4xw    //   /后面表示如何讀取數(shù)據(jù) w表示4個字節(jié)4個字節(jié)讀取,x表示以16進制的方式讀取數(shù)據(jù)材原,4則表示讀取4次
  • 同時也可以通過lldb修改內(nèi)存中的值
memory write 0x100400c68 6
將_no的值改為了6
lldb讀取內(nèi)存.png

更復雜的繼承關系(繼承關系的類的類的對象內(nèi)存分配情況)

面試題:在64bit環(huán)境下沸久,繼承關系的子父類占用內(nèi)存情況如何呢?

/* Person */
@interface Person : NSObject
{
    int _age;
}
@end

@implementation Person
@end

/* Student */
@interface Student : Person
{
    int _no;
}
@end

@implementation Student
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"%zd  %zd",
              class_getInstanceSize([Person class]),
              class_getInstanceSize([Student class])
              );
    }
    return 0;
}

//打印結果如下:
Interview01-OC對象的本質[2872:67593] stu - 16
Interview01-OC對象的本質[2872:67593] person - 16
  • 這道面試題的實質是想問一個Person對象余蟹,一個Student對象分別占用多少內(nèi)存空間卷胯?


    preson對象和student對象內(nèi)存結構.png
  • 我們依次將上面的Student子類跟Person父類轉化成C++結構體寫出來
struct NSObject_IMPL {
    Class isa;//8
};

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS; // 8
    int _age; // 4
}; // 16 內(nèi)存對齊:結構體的大小必須是最大成員大小的倍數(shù)

struct Student_IMPL {
    struct Person_IMPL Person_IVARS; // 16
    int _no; // 4
}; // 16
  • 我們發(fā)現(xiàn)只要是繼承自NSObject的對象,那么底層結構體內(nèi)一定有一個isa指針威酒。
  • 那么他們所占的內(nèi)存空間是多少呢窑睁?單純的將指針和成員變量所占的內(nèi)存相加即可嗎挺峡?上述代碼實際打印的內(nèi)容是16 16,也就是說担钮,person對象和student對象所占用的內(nèi)存空間都為16個字節(jié)橱赠。
  • 其實實際上person對象確實只使用了12個字節(jié)。但是因為內(nèi)存對齊的原因箫津。使person對象也占用16個字節(jié)狭姨。

系統(tǒng)給對象分配內(nèi)存時會遵循內(nèi)存對齊:結構體的大小必須是最大成員大小的倍原則,也就說Person_IMPL結構體中的成員變量(isa跟_age)實際需要12字節(jié)空間苏遥,但是系統(tǒng)根據(jù)原則確分配了16字節(jié)饼拍,所以結果是16字節(jié)。
而** Student_IMPL怎么又成了16字節(jié)呢田炭,上面說了系統(tǒng)給Person_IMPL分配了16字節(jié)师抄,實際占用12字節(jié),還留有4字節(jié)空余教硫,恰好放_no**4字節(jié)的變量司澎,這樣出來的結果就是系統(tǒng)分配16字節(jié)恰好夠Student_IMPL對象使用。

所以栋豫,綜上:
  • 我們總結一下系統(tǒng)給對象分配存儲空間的原則:編譯器在給結構體開辟空間時,首先找到結構體中最寬的基本數(shù)據(jù)類型谚殊,然后尋找內(nèi)存地址能是該基本數(shù)據(jù)類型的整倍的位置丧鸯,作為結構體的首地址。將這個最寬的基本數(shù)據(jù)類型的大小作為對齊模數(shù)嫩絮。
  • 為結構體的一個成員開辟空間之前丛肢,編譯器首先檢查預開辟空間的首地址相對于結構體首地址的偏移是否是本成員的整數(shù)倍,若是剿干,則存放本成員蜂怎,反之,則在本成員和上一個成員之間填充一定的字節(jié)置尔,以達到整數(shù)倍的要求杠步,也就是將預開辟空間的首地址后移幾個字節(jié)。
  • 我們可以總結內(nèi)存對齊為兩個原則:
    • 原則 1. 前面的地址必須是后面的地址正數(shù)倍,不是就補齊榜轿。
    • 原則 2. 整個Struct的地址必須是最大字節(jié)的整數(shù)倍幽歼。
  • 通過上述內(nèi)存對齊的原則我們來看,person對象的第一個地址要存放isa指針需要8個字節(jié)谬盐,第二個地址要存放_age成員變量需要4個字節(jié)甸私,根據(jù)原則一,8是4的整數(shù)倍飞傀,符合原則一皇型,不需要補齊诬烹。然后檢查原則2,目前person對象共占據(jù)12個字節(jié)的內(nèi)存弃鸦,不是最大字節(jié)數(shù)8個字節(jié)的整數(shù)倍绞吁,所以需要補齊4個字節(jié),因此person對象就占用16個字節(jié)空間寡键。
  • 而對于student對象掀泳,我們知道sutdent對象中,包含person對象的結構體實現(xiàn)西轩,和一個int類型的_no成員變量员舵,同樣isa指針8個字節(jié),_age成員變量4個字節(jié)藕畔,_no成員變量4個字節(jié)马僻,剛好滿足原則1和原則2,所以student對象占據(jù)的內(nèi)存空間也是16個字節(jié)注服。

補充:

  • 如果此時在Preson類再加一個height的變量韭邓,那么內(nèi)存是多大呢?
// Person
@interface Person : NSObject
{
    @public
    int _age;
}
@property (nonatomic, assign) int height;
@end

@implementation Person

@end

//Student
@interface Student : Person
{
    int _no;
}
@end

@implementation Student

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"stu - %zd", class_getInstanceSize([Student class]));
        NSLog(@"person - %zd", class_getInstanceSize([Person class]));
       
    }
    return 0;
}
//打印結果如下:
Interview01-OC對象的本質[2872:67593] stu - 24
Interview01-OC對象的本質[2872:67593] person - 16
  • 此時溶弟,Student類實際占用的內(nèi)存空間是24女淑,為什么不是16+4呢,因為在Student類本質的內(nèi)存里面辜御,實質就是isa+age+height+no鸭你,這4個,根據(jù)原則擒权,是最大內(nèi)存的倍數(shù)袱巨,所以是24;

補充:

@interface MJPerson : NSObject
{
    int _age;
    int _height;
    int _no;
}
@end

@implementation MJPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *p = [[MJPerson alloc] init];
        
        NSLog(@"%zd", sizeof(struct MJPerson_IMPL)); // 24
        
        NSLog(@"%zd %zd",
              class_getInstanceSize([MJPerson class]), // 24
              malloc_size((__bridge const void *)(p))); // 32
    }
    return 0;
}
  • 我們依次將上面的MJPerso父類轉化成C++結構體寫出來
struct MJPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;  //8
    int _age;  //4
    int _height;  //4
    int _no;  //4
}; // 計算結構體大小碳抄,本質應該是20愉老,根據(jù)上面講到的原則,內(nèi)存對齊和是最大內(nèi)存的倍數(shù)剖效,所以是24
struct NSObject_IMPL {
    Class isa;
};
  • 但此時malloc_size()函數(shù)打印的結果是32嫉入,為什么呢?
  • 我們通過打斷點璧尸。
    • Debug Workflow -> viewMemory查看


      內(nèi)存查看.png
  • 發(fā)現(xiàn)真的是32個字節(jié)
  • 查看源碼(objc4)劝贸,搜索allocWithZone,查看其.m文件的實現(xiàn)方法
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;
}
  • 里面有一個創(chuàng)建實例的方法
obj = class_createInstance(cls, 0);
  • command點擊進入
id 
class_createInstance(Class cls, size_t extraBytes)
{
    return _class_createInstanceFromZone(cls, extraBytes, nil);
}

  • command點擊進入
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;
}
  • 里面有創(chuàng)建的語句calloc()函數(shù)
obj = (id)calloc(1, size);
//知道這個函數(shù)傳入了一個size逗宁,這個size是從
// size_t size = cls->instanceSize(extraBytes);得到的映九;
  • command進入instanceSize
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;
    }
    //extraBytes是額外的字節(jié)數(shù)
    //通過_objc_rootAllocWithZone()函數(shù)里面的obj = class_createInstance(cls, 0);可以知道,這個額外的字節(jié)數(shù)瞎颗,傳入的是0件甥;
  • 我們發(fā)現(xiàn)這個size是通過alignedInstanceSize() + extraBytes捌议,得到的;
  • command進入alignedInstanceSize()
// Class's ivar size rounded up to a pointer-size boundary.
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }
  • 在這個地方我們知道引有,command進入alignedInstanceSize()函數(shù)是一個叫class_getInstanceSize()的函數(shù)在調(diào)用
size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}
  • 這樣在calloc()函數(shù)里面
obj = (id)calloc(1, size);
//相當于
obj = (id)calloc(1, class_getInstanceSize(Class cls));
//所以這個地方的size傳入的的確是24
  • 此時我們需要查看calloc的底層實現(xiàn)源碼(libmalloc源碼)瓣颅,搜索malloc.c文件,查看他的.m文件譬正,在里面找到calloc方法
void *
calloc(size_t num_items, size_t size)
{
    void *retval;
    retval = malloc_zone_calloc(default_zone, num_items, size);
    if (retval == NULL) {
        errno = ENOMEM;
    }
    return retval;
}
  • command進入malloc_zone_calloc()函數(shù)
void *
malloc_zone_calloc(malloc_zone_t *zone, size_t num_items, size_t size)
{
    void *ptr;
    size_t alloc_size;
    if (malloc_check_start && (malloc_check_counter++ >= malloc_check_start)) {
        internal_check();
    }
    if (os_mul_overflow(num_items, size, &alloc_size) || alloc_size > MALLOC_ABSOLUTE_MAX_SIZE){
        errno = ENOMEM;
        return NULL;
    }

    ptr = zone->calloc(zone, num_items, size);
    
    if (malloc_logger) {
        malloc_logger(MALLOC_LOG_TYPE_ALLOCATE | MALLOC_LOG_TYPE_HAS_ZONE | MALLOC_LOG_TYPE_CLEARED, (uintptr_t)zone,
                (uintptr_t)(num_items * size), 0, (uintptr_t)ptr, 0);
    }
    return ptr;
}
  • 綜述:蘋果在分配內(nèi)存的時候也是有內(nèi)存對齊的宫补,(相似于結構體內(nèi)存對齊,但是又不一樣)這個時候有一個Buckets sized這個東西曾我,看到它是16的倍數(shù)粉怕,所以,到這就明白為什么不是24是32了抒巢,是16的倍數(shù)贫贝;就相當于,分配內(nèi)存的時候是按快分配的蛉谜,16是一塊稚晚,32是一塊,48是一塊型诚。客燕。。狰贯。


    內(nèi)存桶.png
總結
  • 創(chuàng)建一個實例對象也搓,至少需要多少內(nèi)存?
    • 導入#import <objc/runtime.h>
    • class_getInstanceSize([NSObject class]);
  • 創(chuàng)建一個實例對象暮现,實際上分配了多少內(nèi)存?
    • 導入#import <malloc/malloc.h>
    • malloc_size((__bridge const void *)obj);
  • 需要注意的是sizeof是在編譯的時候就確定的內(nèi)存的大小楚昭,例如栖袋,如果是int就是4,如果是指針就是8抚太。塘幅。。
  • 不同類型所占內(nèi)存大心蚱丁:
2019-03-15 10:51:36.718391+0800 iOSProject[28310:10739021] ********64位環(huán)境********
2019-03-15 10:51:36.718741+0800 iOSProject[28310:10739021] bool size:1
2019-03-15 10:51:36.718817+0800 iOSProject[28310:10739021] BOOL size:1
2019-03-15 10:51:36.718856+0800 iOSProject[28310:10739021] char size:1
2019-03-15 10:51:36.718895+0800 iOSProject[28310:10739021] int8_t size:1
2019-03-15 10:51:36.718928+0800 iOSProject[28310:10739021] unsigned char size:1
2019-03-15 10:51:36.718960+0800 iOSProject[28310:10739021] Boolean size:1
2019-03-15 10:51:36.718993+0800 iOSProject[28310:10739021] short size:2
2019-03-15 10:51:36.719025+0800 iOSProject[28310:10739021] int16_t size:2
2019-03-15 10:51:36.719059+0800 iOSProject[28310:10739021] unsigned short size:2
2019-03-15 10:51:36.719092+0800 iOSProject[28310:10739021] unichar size:2
2019-03-15 10:51:36.719125+0800 iOSProject[28310:10739021] int size:4
2019-03-15 10:51:36.719158+0800 iOSProject[28310:10739021] int32_t size:4
2019-03-15 10:51:36.719190+0800 iOSProject[28310:10739021] unsigned int size:4
2019-03-15 10:51:36.719222+0800 iOSProject[28310:10739021] boolean_t size:4
2019-03-15 10:51:36.719297+0800 iOSProject[28310:10739021] long size:8
2019-03-15 10:51:36.719334+0800 iOSProject[28310:10739021] NSInteger size:8
2019-03-15 10:51:36.719367+0800 iOSProject[28310:10739021] long size:8
2019-03-15 10:51:36.719401+0800 iOSProject[28310:10739021] unsigned long size:8
2019-03-15 10:51:36.719435+0800 iOSProject[28310:10739021] NSUInteger size:8
2019-03-15 10:51:36.719471+0800 iOSProject[28310:10739021] long long size:8
2019-03-15 10:51:36.719503+0800 iOSProject[28310:10739021] double size:8

OC對象的分類

面試題:OC對象都有哪些呢电媳?

  • 示例代碼
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

/* Person */ 
@interface Person : NSObject <NSCopying>
{
    @public
    int _age;
}
@property (nonatomic, assign) int height;
- (void)personMethod;
+ (void)personClassMethod;
@end

@implementation Person
- (void)personMethod {}
+ (void)personClassMethod {}
@end

/* Student */
@interface Student : Person <NSCoding>
{
    @public
    int _no;
}
@property (nonatomic, assign) int score;
- (void)studentMethod;
+ (void)studentClassMethod;
@end

@implementation Student
- (void)studentMethod {}
+ (void)studentClassMethod {}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {      
        NSObject *object1 = [[NSObject alloc] init];
        NSObject *object2 = [[NSObject alloc] init];

        Student *stu = [[Student alloc] init];
        [Student load];

        Person *p1 = [[Person alloc] init];
        p1->_age = 10;
        [p1 personMethod];
        [Person personClassMethod];
        Person *p2 = [[Person alloc] init];
        p2->_age = 20;
    }
    return 0;
}

Objective-C中的對象,簡稱OC對象庆亡,主要可以分為3種

  • instance對象(實例對象)
  • class對象(類對象)
  • meta-class對象(元類對象)

instance對象(實例對象)

  • instance對象就是通過類alloc出來的對象匾乓,每次調(diào)用alloc都會產(chǎn)生新的instance對象
NSObjcet *object1 = [[NSObjcet alloc] init];
NSObjcet *object2 = [[NSObjcet alloc] init];
  • object1、object2是NSObject的instance對象(實例對象)
  • object1和object2都是NSObject的instace對象(實例對象)又谋,但他們是不同的兩個對象拼缝,并且分別占據(jù)著兩塊不同的內(nèi)存娱局。
  • instance對象在內(nèi)存中存儲的信息包括
    • isa指針
    • 其他成員變量


      Preson類.png
  • 如圖,我們創(chuàng)建了一個Preson類咧七,并且創(chuàng)建了兩個實例變量衰齐,那么實例對象的內(nèi)存如圖所示:


    instance對象內(nèi)存.png

衍生問題:在上圖實例對象中根本沒有看到方法,那么實例對象的方法的代碼放在什么地方呢继阻?那么類的方法的信息耻涛,協(xié)議的信息,屬性的信息都存放在什么地方呢瘟檩?

class對象(類對象)

  • 我們通過class方法或runtime方法得到一個class對象抹缕。class對象也就是類對象
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = [NSObject class];

// runtime
Class objectClass4 = object_getClass(object1);
Class objectClass5 = object_getClass(object2);
NSLog(@"%p %p %p %p %p", objectClass1, objectClass2, objectClass3, objectClass4, objectClass5);

//內(nèi)存打印地址如下:
objectClass1 = 0x7fff97528118 objectClass2 = 0x7fff97528118 objectClass3 = 0x7fff97528118 objectClass4 = 0x7fff97528118 objectClass5 = 0x7fff97528118

// 而調(diào)用類對象的class方法時得到還是類對象,無論調(diào)用多少次都是類對象
Class cls = [[NSObject class] class];
Class objectClass6 = [NSObject class];
NSLog(@"objectClass = %p cls = %p", objectClass6, cls); // 后面兩個地址相同芒帕,說明多次調(diào)用class得到的還是類對象

//打印結果如下:
objectClass = 0x7fff97528118 cls = 0x7fff97528118

  • 每一個類在內(nèi)存中有且只有一個class對象歉嗓。可以通過打印內(nèi)存地址證明
class對象在內(nèi)存中存儲的信息主要包括
  • isa指針
  • superclass指針
  • 類的屬性信息(@property)背蟆,類的成員變量信息(ivar)
  • 類的對象方法信息(instance method)鉴分,類的協(xié)議信息(protocol)


    class對象在內(nèi)存中存儲的信息圖例.png
  • 成員變量的值時存儲在實例對象中的,因為只有當我們創(chuàng)建實例對象的時候才為成員變賦值带膀。但是成員變量叫什么名字志珍,是什么類型,只需要有一份就可以了垛叨。所以存儲在class對象中伦糯。
  • 類方法放在那里?

meta-class對象(元類對象)

  • 只能是通過class對象獲取到meta-class對象嗽元,通過下面的方法獲取到敛纲。
//runtime中傳入類對象此時得到的就是元類對象
Class objectMetaClass = object_getClass([NSObject class]);
NSLog(@"objectMetaClass = %p",objectMetaClass);

//內(nèi)存打印地址如下:
objectMetaClass = 0x7fff975280f0


// 而調(diào)用類對象的class方法時得到還是類對象,無論調(diào)用多少次都是類對象
Class cls = [[NSObject class] class];
Class objectClass3 = [NSObject class];
class_isMetaClass(objectMetaClass) // 判斷該對象是否為元類對象
NSLog(@"%p %p %p", objectMetaClass, objectClass3, cls); // 后面兩個地址相同剂癌,說明多次調(diào)用class得到的還是類對象

//檢查是否為元類對象
BOOL ismetaclass = class_isMetaClass(objectMetaClass);// 判斷該對象是否為元類對象
NSLog(@"objectMetaClass 是否是元類對象 - %ld",ismetaclass);

//打印結果如下:
objectMetaClass 是否是元類對象 - 1

  • 每一個類的meta-class對象在內(nèi)存中有且只有一個淤翔,class對象跟meta-class對象結構一樣,都是*struct objc_class Class佩谷,但是用途不一樣旁壮。

  • meta-class對象在內(nèi)存中存儲的信息包括:

    • isa指針
    • superclass指針
    • 類的類方法信息(class-method)


      元類meta-class對象在內(nèi)存存儲的信息圖例.png
  • meta-class對象和class對象的內(nèi)存結構是一樣的,所以meta-class中也有類的屬性信息谐檀,類的對象方法信息等成員變量抡谐,但是其中的值可能是空的。

補充
objc_getClass()和object_getClass()的區(qū)別桐猬;那么[NSObject class]中的class有什么不同呢麦撵?
  • objc_getClass()函數(shù)
Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;

    // NO unconnected, YES class handler
    return look_up_class(aClassName, NO, YES);
}
//分析:接收的是類名;其實質就是字符串;

  • 里面有一個look_up_class()函數(shù)厦坛,返回的是這個函數(shù)五垮;
Class 
look_up_class(const char *name, 
              bool includeUnconnected __attribute__((unused)), 
              bool includeClassHandler __attribute__((unused)))
{
    if (!name) return nil;

    Class result;
    bool unrealized;
    {
        rwlock_reader_t lock(runtimeLock);
        result = getClass(name);
        unrealized = result  &&  !result->isRealized();
    }
    if (unrealized) {
        rwlock_writer_t lock(runtimeLock);
        realizeClass(result);
    }
    return result;
}
  • 我們分析發(fā)現(xiàn)return result;看result是什么,發(fā)現(xiàn)是result = getClass(name);那么getClass()函數(shù)內(nèi)部實現(xiàn)是
static Class getClass(const char *name)
{
    runtimeLock.assertLocked();

    // Try name as-is
    Class result = getClass_impl(name);
    if (result) return result;

    // Try Swift-mangled equivalent of the given name.
    if (char *swName = copySwiftV1MangledName(name)) {
        result = getClass_impl(swName);
        free(swName);
        return result;
    }

    return nil;
}
  • 里面有語句Class result = getClass_impl(name);杜秸,command進入getClass_impl()函數(shù)放仗;
static Class getClass_impl(const char *name)
{
    runtimeLock.assertLocked();

    // allocated in _read_images
    assert(gdb_objc_realized_classes);

    // Try runtime-allocated table
    Class result = (Class)NXMapGet(gdb_objc_realized_classes, name);
    if (result) return result;

    // Try table from dyld shared cache
    return getPreoptimizedClass(name);
}
  • 在這段代碼我們可以發(fā)現(xiàn)有一個Class result = (Class)NXMapGet(gdb_objc_realized_classes, name);語句,可以發(fā)現(xiàn)是根據(jù)一個字符串找到一個class的結構,所以,返回的其實就是類對象窗市,所以唱逢,到這就可以分析出,objc_getClass()這個函數(shù)就是將一個類名傳進入,返回一個類對象;

  • object_getClass()函數(shù)

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    //這句的意思是,如果傳進來的是银室,instance對象,就返回class對象励翼;如果傳進來的是蜈敢,class對象,就返回meta-class對象汽抚;如果傳進來是meta-class對象抓狭,就返回NSObject(基類)的meta-class對象;
    //getIsa()其實就是返回的是isa造烁;
    else return Nil;
}
//分析:接收的是類對象否过;
  • [NSObject class]中的class
    • -(Class) class
    • +(Class) class
    • 返回的都是類對象
  • 總結:
    • objc_getClass()這個函數(shù)就是將一個類名傳進入,返回一個類對象惭蟋。
    • object_getClass()函數(shù)這個函數(shù)就是將一個類對象傳進入苗桂,返回的是元類對象,如果傳進來的是告组,instance對象煤伟,就返回class對象;如果傳進來的是惹谐,class對象持偏,就返回meta-class對象驼卖;如果傳進來是meta-class對象氨肌,就返回NSObject(基類)的meta-class對象。
    • [NSObject class]中的class有兩種情況酌畜,-(Class)class和+ (Class) class怎囚,都返回的就是類對象;

面試題:對象的isa指針指向哪里。

  • 首先說明
[Preson personClassMethod];
//實質就是給這個Preson類發(fā)送一個消息
//objc_msgSend([Preson class] @selector(personClassMethod))
  • 當對象調(diào)用實例方法的時候恳守,我們上面講到考婴,實例方法信息是存儲在class類對象中的,那么要想找到實例方法催烘,就必須找到class類對象沥阱,那么此時isa的作用就來了。
  • 代碼如下
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

// MJPerson
@interface MJPerson : NSObject <NSCopying>
{
    @public
    int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

@implementation MJPerson

- (void)test
{
    
}

- (void)personInstanceMethod
{
    
}
+ (void)personClassMethod
{
    
}
- (id)copyWithZone:(NSZone *)zone
{
    return nil;
}
@end

// MJStudent
@interface MJStudent : MJPerson <NSCoding>
{
@public
    int _weight;
}
@property (nonatomic, assign) int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end

@implementation MJStudent
- (void)test
{
    
}
- (void)studentInstanceMethod
{
    
}
+ (void)studentClassMethod
{
    
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
    return nil;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJStudent *student = [[MJStudent alloc] init];
        
        
        
        [student test];
        

        [student personInstanceMethod];
        
        [student init];
        
        [MJStudent studentClassMethod];
        
        [MJStudent personClassMethod];
        
        [MJStudent load];
    }
    return 0;
}
  • 根據(jù)上例代碼伊群,知道MJStudent繼承于MJPerson考杉,MJPerson繼承于NSObject
MJStudent *student = [[MJStudent alloc]init];
//方法1:調(diào)用實例方法
[student studentInstanceMethod];

  • 方法1:student實例對象調(diào)用了實例方法,我們在前面講到過舰始,實例方法信息存儲在class對象中崇棠,這時候instace對象中存儲的isa指針起到作用了,instace對象中的isa指針指向class對象丸卷,我們通過isa指針找到class對象枕稀,進而找到實例方法列表,調(diào)用對應方法谜嫉。


    對象方法調(diào)用軌跡.png
//方法2:調(diào)用類方法
[MJStudent studentClassMethod];
  • 方法2:MJStudent類對象調(diào)用了類方法萎坷,我們在前面講到過,類方法信息存儲在meta-class對象中骄恶,這時候class對象中存儲的isa指針起到作用了食铐,class對象中的isa指針指向meta-class對象,我們通過isa指針找到meta-class對象僧鲁,進而找到類方法列表虐呻,調(diào)用對應方法。
  • 如上圖所示寞秃;
總結
  • 當對象調(diào)用實例方法的時候斟叼,實例方法信息是存儲在class類對象中的,instance的isa指向class春寿,當調(diào)用對象方法時朗涩,通過instance的isa找到class,最后找到對象方法的實現(xiàn)進行調(diào)用绑改。
  • 當類對象調(diào)用類方法的時候谢床,同上,類方法是存儲在meta-class元類對象中的厘线。那么要找到類方法识腿,就需要找到meta-class元類對象,而class類對象的isa指針就指向元類對象造壮,class的isa指向meta-class渡讼,當調(diào)用類方法時,通過class的isa找到meta-class,最后找到類方法的實現(xiàn)進行調(diào)用
  • 具體如下圖:


    isa指針指向圖例.png

    對象調(diào)用父類對象方法圖例.png
OC對象的superclass指針指向位置

當對象調(diào)用其父類對象方法的時候成箫,又是怎么找到父類對象方法的呢展箱?


對象調(diào)用父類對象方法圖例.png
//方法1:調(diào)用父類的實例方法
[student personInstanceMethod];
//方法2:調(diào)用父類的類方法
[MJStudent personClassMethod];
  • 方法1:當給student實例對象發(fā)送personInstanceMethod消息時,student實例對象會通過isa指針找到對應MJStudent類對象蹬昌,因為類對象中存儲中對象方法信息混驰,先從MJStudent類對象的實例方法信息中查找對應的方法,如果找到進行相應皂贩,沒找到則繼續(xù)向父類查找账胧,那么子類怎么才能找到父類呢,這時候需要用到superclass指針了先紫,通過superclass指針找到MJPerson的類對象治泥,繼續(xù)從類對象那個的實例方法中查找,如果找到進行相應遮精,沒找到則繼續(xù)通過superclass查找基類NSObject類對象方法列表居夹,如果還沒找到,返回nil本冲,就是常見的報錯信息准脂,找不到此方法。
  • 方法2:跟方法1類似檬洞,當給MJStudent類對象發(fā)送personClassMethod消息時狸膏,MJStudent類對象會通過isa指針找到對應MJStudent元類對象,因為元類對象中存儲中類方法信息添怔,先從MJStudent元類對象的類信息中查找對應的方法湾戳,如果找到進行相應,沒找到則繼續(xù)向父類查找广料,那么子類怎么才能找到父類呢砾脑,這時候需要用到superclass指針了,通過superclass指針找到MJPerson的元類對象艾杏,繼續(xù)從元類對象那個的類方法中查找韧衣,如果找到進行相應,沒找到則繼續(xù)通過superclass查找基類NSObject元類對象方法列表购桑,如果還沒找到畅铭,這個時候跟方法1的查找不太一樣了,如果NSObject的元類對象的類方法中找到勃蜘,就從NSObject的類方法的實例方法中去查找硕噩,還沒有找到,則返回nil元旬,就是常見的報錯信息榴徐,找不到此方法。


    isa-superclass.png
總結
  • 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指針的指向真的如上面所說柒莉?代碼求證isa指針指向是否正確;

NSObject *object = [[NSObject alloc] init];//instance對象
Class objectClass = [NSObject class];//類對象 
Class objectMetaClass = object_getClass([NSObject class]);//元類對象
NSLog(@"object - %p objectClass - %p objectMetaClass - %p", object, objectClass, objectMetaClass);

//打印結果如下:
object - 0x10051e0b0    //instance對象內(nèi)存地址
objectClass - 0x7fff9abb6118 //類對象內(nèi)存地址
objectMetaClass - 0x7fff9abb60f0  //元類對象內(nèi)存地址

  • 代碼如下
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

// MJPerson
@interface MJPerson : NSObject <NSCopying>
{
@public
    int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end

@implementation MJPerson

- (void)test
{
    
}

- (void)personInstanceMethod
{
    
}
+ (void)personClassMethod
{
    
}
- (id)copyWithZone:(NSZone *)zone
{
    return nil;
}
@end

// MJStudent
@interface MJStudent : MJPerson <NSCoding>
{
@public
    int _weight;
}
@property (nonatomic, assign) int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end

@implementation MJStudent
- (void)test
{
    
}
- (void)studentInstanceMethod
{
    
}
+ (void)studentClassMethod
{
    
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
    return nil;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
    
}
@end

struct mj_objc_class {
    Class isa;
    Class superclass;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        
    MJPerson *person = [[MJPerson alloc] init];
    Class personClass = [MJPerson class];
    NSLog(@"%p %p %p", person, personClass, personMetaClass);

    }
    return 0;
}
  • 在NSLog()函數(shù)打斷點沽翔,如圖兢孝,我們可以看出來


    Preson類的結構.png
  • 如下,通過控制臺打印相應對象的isa指針


    地址打印.png
  • 此時我們發(fā)現(xiàn)這兩個地址是不一樣的仅偎,為什么呢跨蟹?

  • 這是因為從64bit開始,isa需要進行一次位運算橘沥,才能計算出真實地址窗轩。而位運算的值我們可以通過下載objc源代碼找到。


    ISA_MASK.png
  • 我們通過位運算進行驗證座咆。


    計算之后的打印結果.png
  • 我們發(fā)現(xiàn)痢艺,person-isa指針地址0x001d8001000014c9經(jīng)過同0x00007ffffffffff8位運算,得出personClass的地址0x00000001000014c8

  • 果然跟程序打印出來的結果一樣介陶,這足以證明上面總結的isa指針指向的正確性腹备。

  • 但我們再次嘗試驗證類方法的isa指針指向的元類對象的內(nèi)存地址跟程序自然打印的是否一樣的時候,發(fā)現(xiàn)了如下問題


    類對象isa指針內(nèi)存地址.png
  • 同時也發(fā)現(xiàn)左邊objectClass對象中并沒有isa指針斤蔓。


    presonClass.png
  • 我們來到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指針植酥,為了拿到isa指針的地址,我們自己創(chuàng)建一個同樣的結構體并通過強制轉化拿到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);

  • 此時我們重新驗證一下


    類對象位運算結果.png
補充
  • 對superclass進行分析友驮,看是不是和isa是一樣的
  • 代碼如下
Class personClass = [MJPerson class];
Class studentClass = [MJStudent class];

NSLog(@"1111");
  • 在命令號輸入p personClass->superclass
  • 我們發(fā)現(xiàn)和isa是一樣的,找不到驾锰;


    打印superclass地址.png
  • 我們在mj_objc_class結構體里面加一個變量superclass
struct mj_objc_class {
    Class isa;
    Class superclass;
};

  • 和isa處理一樣
struct mj_objc_class *personClass = (__bridge struct mj_objc_class *)([MJPerson class]);

struct mj_objc_class *studentClass = (__bridge struct mj_objc_class *)([MJStudent class]);

NSLog(@"1111");
驗證superclass.png

本文面試題總結

  • 一個NSObject對象占用多少內(nèi)存卸留?
    • 答:一個指針變量所占用的大小(64bit占8個字節(jié)椭豫,32bit占4個字節(jié))
  • 對象的isa指針指向哪里耻瑟?
    • instance對象(實例對象)
    • class對象(類對象)
    • meta-class對象(元類對象)
  • OC對象的isa指針指向哪里呢旨指?
    • instance對象的isa指針指向class對象,class對象的isa指針指向meta-class對象喳整,meta-class對象的isa指針指向基類的meta-class對象谆构,基類自己的isa指針也指向自己。
  • OC的類信息存放在哪里框都?
    • nstance對象(實例方法):存放isa指針搬素,成員變量的具體數(shù)據(jù)
    • class對象(類對象):存放isa指針,superclass指針魏保,類的成員變量(ivar)熬尺,類的屬性信息(property),類的協(xié)議信息(protocol)谓罗,類的方法列表(instance method list)
    • meta-class對象(元類對象:存放isa指針粱哼,superclass指針,類的方法列表(class method list)
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末檩咱,一起剝皮案震驚了整個濱河市皂吮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌税手,老刑警劉巖蜂筹,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芦倒,居然都是意外死亡艺挪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門兵扬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來麻裳,“玉大人,你說我怎么就攤上這事器钟〗蚩樱” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵傲霸,是天一觀的道長疆瑰。 經(jīng)常有香客問我,道長昙啄,這世上最難降的妖魔是什么穆役? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮梳凛,結果婚禮上耿币,老公的妹妹穿的比我還像新娘。我一直安慰自己韧拒,他們只是感情好淹接,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布十性。 她就那樣靜靜地躺著,像睡著了一般塑悼。 火紅的嫁衣襯著肌膚如雪劲适。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天拢肆,我揣著相機與錄音,去河邊找鬼靖诗。 笑死郭怪,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的刊橘。 我是一名探鬼主播鄙才,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼促绵!你這毒婦竟也來了攒庵?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤败晴,失蹤者是張志新(化名)和其女友劉穎浓冒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尖坤,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡稳懒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了慢味。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片场梆。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖纯路,靈堂內(nèi)的尸體忽然破棺而出或油,到底是詐尸還是另有隱情,我是刑警寧澤驰唬,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布顶岸,位于F島的核電站,受9級特大地震影響叫编,放射性物質發(fā)生泄漏蜕琴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一宵溅、第九天 我趴在偏房一處隱蔽的房頂上張望凌简。 院中可真熱鬧,春花似錦恃逻、人聲如沸雏搂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凸郑。三九已至裳食,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芙沥,已是汗流浹背诲祸。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留而昨,地道東北人救氯。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像歌憨,于是被迫代替她去往敵國和親着憨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355