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

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

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

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

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


    OC代碼的轉(zhuǎn)化過程.png
  • 所以OC的面向?qū)ο蠖际腔贑\C++的數(shù)據(jù)結構實現(xiàn)的。
  • 思考:OC的對象、類主要是基于C\C++的什么數(shù)據(jù)結構實現(xiàn)的?
    • 結構體
  • 我們通過創(chuàng)建OC文件及對象,并將OC文件轉(zhuǎn)化為C++文件來探尋OC對象的本質(zhì),將OC代碼轉(zhuǎn)換成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文件轉(zhuǎn)化為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本質(zhì),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
  • 最終轉(zhuǎn)化為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的本質(zhì)調(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代碼轉(zhuǎn)C++文件的方式進行轉(zhuǎn)換。我們從 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類轉(zhuǎn)化為C++的結構體后第一項是struct NSObject_IMPL ,而通過上面的實驗我們知道NSObject_IMPL內(nèi)部其實就是Class isa
struct NSObject_IMPL {
    Class isa;
};
  • 那么我們假設 struct NSObject_IMPL NSObject_IVARS; 等價于 Class isa;可以將上述代碼轉(zhuǎn)化為
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 {
            // 強制轉(zhuǎn)化
            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對象強轉(zhuǎn)成Student_IMPL的結構體。也就是說把一個指向oc對象的指針,指向這種結構體。由于我們之前猜想,對象在內(nèi)存中的布局與結構體在內(nèi)存中的布局相同,那么如果可以轉(zhuǎn)化成功,說明我們的猜想正確。由此說明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對象的本質(zhì)[2872:67593] stu - 16
Interview01-OC對象的本質(zhì)[2872:67593] person - 16
  • 這道面試題的實質(zhì)是想問一個Person對象,一個Student對象分別占用多少內(nèi)存空間?


    preson對象和student對象內(nèi)存結構.png
  • 我們依次將上面的Student子類跟Person父類轉(zhuǎn)化成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對象的本質(zhì)[2872:67593] stu - 24
Interview01-OC對象的本質(zhì)[2872:67593] person - 16
  • 此時,Student類實際占用的內(nèi)存空間是24,為什么不是16+4呢,因為在Student類本質(zhì)的內(nèi)存里面,實質(zhì)就是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父類轉(zhuǎn)化成C++結構體寫出來
struct MJPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;  //8
    int _age;  //4
    int _height;  //4
    int _no;  //4
}; // 計算結構體大小,本質(zhì)應該是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)存大?。?/li>
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);
}
//分析:接收的是類名;其實質(zhì)就是字符串;

  • 里面有一個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];
//實質(zhì)就是給這個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)建一個同樣的結構體并通過強制轉(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);

  • 此時我們重新驗證一下


    類對象位運算結果.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)存?
    • 答:一個指針變量所占用的大?。?4bit占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)
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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