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

本篇主要是對(duì)小碼哥底層視頻學(xué)習(xí)的總結(jié)。方便日后復(fù)習(xí)。

本篇學(xué)習(xí)總結(jié):

  • NSObject對(duì)象/自定義類的對(duì)象/繼承關(guān)系的類的類的對(duì)象內(nèi)存分配情況以及類信息情況
  • OC對(duì)象類別有哪些呢?
  • OC對(duì)象的類信息存儲(chǔ)位置在哪里呢?
  • OC對(duì)象中常說的isa指針是怎么回事呢?
  • OC對(duì)象中常說的superclass指針怎么回事呢?

好了,帶著問題,我們一一開始閱讀吧 ??

一.NSObject 對(duì)象內(nèi)存分配情況

1.面試題:在64bit環(huán)境下,一個(gè)NSObject對(duì)象占用多少內(nèi)存?

探尋OC對(duì)象的本質(zhì),我們平時(shí)編寫的Objective-C代碼,轉(zhuǎn)化成底層都是C\C++代碼。

OC代碼轉(zhuǎn)化過程.png

OC代碼如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

我們要想看到OC代碼轉(zhuǎn)化為C++文件,需要通過命令行進(jìn)行操作,現(xiàn)在將OC的main.m 文件轉(zhuǎn)化為C++文件,
第一個(gè)方法是安裝插件轉(zhuǎn)換:


oc代碼轉(zhuǎn)c++插件.png

第二個(gè)方法是直接 cd main.m文件所在文件夾


cd main.m文件所在文件夾.png

然后再執(zhí)行下面的命令行工具(用于mac命令行項(xiàng)目)

clang -rewrite-objc main.m -o main.cpp // 這種方式?jīng)]有指定架構(gòu)例如arm64架構(gòu) 其中cpp代表(c plus plus)
生成 main.cpp

我們還可以指定架構(gòu)模式的命令行,使用Xcode工具xrun(用于iOS應(yīng)用)

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 
生成 main-arm64.cpp 

下面提示代碼c++轉(zhuǎn)化成功:


轉(zhuǎn)化成功.png
轉(zhuǎn)化成功后的文件.png

我們將生成的文件添加到項(xiàng)目中,不需要編譯


C++轉(zhuǎn)換文件.png

我們打開main-arm64.cpp文件,搜索NSObject,可以找到NSObjcet_IMPL (IMPL代表 implementation 實(shí)現(xiàn)),代碼如下:

struct NSObject_IMPL {
    Class isa;
};

發(fā)現(xiàn)里面只有一個(gè)Class類型的isa成員變量,順勢(shì)點(diǎn)進(jìn)入Class查看一下它的結(jié)構(gòu),代碼如下:

typedef struct objc_class *Class;
查看 objc_class,結(jié)構(gòu)如下:
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;

原來typedef struct objc_class Class就是一種結(jié)構(gòu)體的指針。

這時(shí)候我們回到第一個(gè)面試題,一個(gè)NSObject對(duì)象在內(nèi)存中占用多少內(nèi)存,其實(shí)就是isa結(jié)構(gòu)體類型的指針在內(nèi)存中占用的空間,如果64bit占用8個(gè)字節(jié),如果32bit占用4個(gè)字節(jié)。咱們這里探討的是64bit,也就是說一個(gè)NSObjec對(duì)象所占用的內(nèi)存是8個(gè)字節(jié)。到這里我們已經(jīng)可以基本解答第一個(gè)問題。但是我們發(fā)現(xiàn)NSObject對(duì)象中還有很多方法,那這些方法不占用內(nèi)存空間嗎?其實(shí)類的方法等也占用內(nèi)存空間,但是這些方法所占用的存儲(chǔ)空間并不在NSObject對(duì)象中,如果是自定義的類,又是如何計(jì)算內(nèi)存呢,我們繼續(xù)探討OC對(duì)象本質(zhì)問題。

二.自定義類的實(shí)例對(duì)象內(nèi)存分配情況

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

首先創(chuàng)建一個(gè)Student

@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];
        stu->_no = 4;
        stu->_age = 5;
        
        NSLog(@"%zd", class_getInstanceSize([Student class]));
        NSLog(@"%zd", malloc_size((__bridge const void *)stu));

        struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
        NSLog(@"no is %d, age is %d", stuImpl->_no, stuImpl->_age);
    }
    return 0;
}

我們按照上面的OC代碼轉(zhuǎn)C++文件的方式進(jìn)行轉(zhuǎn)換。我們從 main-arm64.cpp 文件中搜索 Student,搜索結(jié)果代碼如下:

struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
    int _age;
};

我們發(fā)現(xiàn)Student類轉(zhuǎn)化為C++的結(jié)構(gòu)體后第一項(xiàng)是struct NSObject_IMPL ,前面探討NSObject對(duì)象的時(shí)候?qū)戇^

struct NSObject_IMPL {
    Class isa;
};

我們將這部分代碼進(jìn)行替換,替換結(jié)果如下:

struct Student_IMPL {
    Class *isa;
    int _no;
    int _age;
};

遵循上面計(jì)算NSObject對(duì)象內(nèi)存的方式,結(jié)構(gòu)體內(nèi)的各個(gè)成員變量占用內(nèi)存總和就是結(jié)構(gòu)體占用總的內(nèi)存大小,咱們給Student對(duì)象計(jì)算一下內(nèi)存大小吧:
isa指針8個(gè)字節(jié)空間+int類型_no4個(gè)字節(jié)空間+int類型_age4個(gè)字節(jié)空間共16個(gè)字節(jié)空間

上面的方法是根據(jù)類型推算出來的內(nèi)存大小,我們還可以根據(jù)代碼計(jì)算出來

 NSLog(@"NSObject = %zd",class_getInstanceSize([NSObject class]));
//類對(duì)象實(shí)際需要內(nèi)存大小
 NSLog(@"Student = %zd", class_getInstanceSize([Student class]));
//系統(tǒng)分配
 NSLog(@"Student = %zd", malloc_size((__bridge const void *)stu));

OC對(duì)象本身占用內(nèi)存大小.png

窺探內(nèi)存結(jié)構(gòu)
我們還需要進(jìn)一步直觀的看到內(nèi)存數(shù)據(jù),那怎么做呢?
方式一:通過打斷點(diǎn)
Debug Workflow -> viewMemory address中輸入一個(gè)NSObject對(duì)象的地址,stu對(duì)象的內(nèi)存地址查看方式也是同樣的操作。

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

查看結(jié)果.png

從上圖中,我們可以發(fā)現(xiàn)讀取數(shù)據(jù)從高位數(shù)據(jù)開始讀,查看前16位字節(jié),每四個(gè)字節(jié)讀出的數(shù)據(jù)為
16進(jìn)制 0x0000004(4字節(jié)) 0x0000005(4字節(jié)) isa的地址為 00D1081000001119(8字節(jié))

方式二:通過lldb指令xcode自帶的調(diào)試器
先看幾個(gè)常用的命令行

memory read 0x10074c450
// 簡寫  x 0x10074c450

// 增加讀取條件
// memory read/數(shù)量格式字節(jié)數(shù)  內(nèi)存地址
// 簡寫 x/數(shù)量格式字節(jié)數(shù)  內(nèi)存地址
// 格式 x是16進(jìn)制,f是浮點(diǎn),d是10進(jìn)制
// 字節(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個(gè)字節(jié)4個(gè)字節(jié)讀取,x表示以16進(jìn)制的方式讀取數(shù)據(jù),4則表示讀取4次

同時(shí)也可以通過lldb修改內(nèi)存中的值

memory write 0x100400c68 6
將_no的值改為了6
libo 查看內(nèi)存結(jié)果圖.png
三.繼承關(guān)系的類的類的對(duì)象內(nèi)存分配情況

3.面試題:在64bit環(huán)境下,繼承關(guān)系的子父類占用內(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;
}
//打印結(jié)果如下:
Interview01-OC對(duì)象的本質(zhì)[2872:67593] stu - 24
Interview01-OC對(duì)象的本質(zhì)[2872:67593] person - 16

其實(shí)這道題主要考察的面試題是什么呢?繼承類的內(nèi)存大小如何計(jì)算呢?
我們依次將上面的Student子類跟Person父類轉(zhuǎn)化成C++結(jié)構(gòu)體寫出來

struct NSObject_IMPL {
    Class isa;//8
};

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

struct Student_IMPL {
    struct Person_IMPL Person_IVARS; // 16
    int _no; // 4
}; // 16

這時(shí)候你會(huì)疑問了Person_IMPL 不是占用12個(gè)字節(jié)嗎,怎么顯示16呢?那是因?yàn)橄到y(tǒng)給對(duì)象分配內(nèi)存時(shí)會(huì)遵循內(nèi)存對(duì)齊:結(jié)構(gòu)體的大小必須是最大成員大小的倍原則,也就說Person_IMPL結(jié)構(gòu)體中的成員變量(isa_age)實(shí)際需要12字節(jié)空間,但是系統(tǒng)根據(jù)原則確分配了16字節(jié),所以結(jié)果是16字節(jié)。
而** Student_IMPL怎么又成了16字節(jié)呢,上面說了系統(tǒng)給Person_IMPL分配了16字節(jié),實(shí)際占用12字節(jié),還留有4字節(jié)空余,恰好放_no**4字節(jié)的變量,這樣出來的結(jié)果就是系統(tǒng)分配16字節(jié)恰好夠Student_IMPL對(duì)象使用。

敲黑板了?。?!

所以,綜上,我們總結(jié)一下系統(tǒng)給對(duì)象分配存儲(chǔ)空間的原則:編譯器在給結(jié)構(gòu)體開辟空間時(shí),首先找到結(jié)構(gòu)體中最寬的基本數(shù)據(jù)類型,然后尋找內(nèi)存地址能是該基本數(shù)據(jù)類型的整倍的位置,作為結(jié)構(gòu)體的首地址。將這個(gè)最寬的基本數(shù)據(jù)類型的大小作為對(duì)齊模數(shù)。
為結(jié)構(gòu)體的一個(gè)成員開辟空間之前,編譯器首先檢查預(yù)開辟空間的首地址相對(duì)于結(jié)構(gòu)體首地址的偏移是否是本成員的整數(shù)倍,若是,則存放本成員,反之,則在本成員和上一個(gè)成員之間填充一定的字節(jié),以達(dá)到整數(shù)倍的要求,也就是將預(yù)開辟空間的首地址后移幾個(gè)字節(jié)。
我們可以總結(jié)內(nèi)存對(duì)齊為兩個(gè)原則:
原則 1. 前面的地址必須是后面的地址正數(shù)倍,不是就補(bǔ)齊。
原則 2. 整個(gè)Struct的地址必須是最大字節(jié)的整數(shù)倍。

如果有興趣可以進(jìn)一步研究底層實(shí)現(xiàn),這里我就做個(gè)學(xué)習(xí)總結(jié).

四.OC對(duì)象的類別以及存儲(chǔ)信息

4.面試題:OC對(duì)象都有哪些呢?
5.面試題:OC的類信息存儲(chǔ)在哪里呢?

先來一段代碼

#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;
}

OC對(duì)象類分為幾類呢?

  • instance對(duì)象(實(shí)例對(duì)象)
  • class對(duì)象(類對(duì)象)
  • meta-class對(duì)象(元類對(duì)象)

instance對(duì)象
通過類alloc 出來的對(duì)象,每次調(diào)用alloc 都會(huì)產(chǎn)生新的instance對(duì)象。

NSObjcet *object1 = [[NSObjcet alloc] init];
NSObjcet *object2 = [[NSObjcet alloc] init];

//內(nèi)存打印地址如下:
object1 = 0x100723a60 object2 = 0x100723720

object1和object2都是NSObject 的instance對(duì)象(實(shí)例對(duì)象),但是是兩個(gè)不同的對(duì)象,從打印結(jié)果就能看出來,它們分別占據(jù)不同的內(nèi)存地址。
instance對(duì)象在內(nèi)存中存儲(chǔ)的信息包括:
1.isa指針
2.成員變量具體的數(shù)據(jù)

instance對(duì)象存儲(chǔ)信息.png

class對(duì)象
我們通過class方法或者runtime方法得到一個(gè)class對(duì)象,class對(duì)象也就是類對(duì)象

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)用類對(duì)象的class方法時(shí)得到還是類對(duì)象,無論調(diào)用多少次都是類對(duì)象
Class cls = [[NSObject class] class];
Class objectClass6 = [NSObject class];
NSLog(@"objectClass = %p cls = %p", objectClass6, cls); // 后面兩個(gè)地址相同,說明多次調(diào)用class得到的還是類對(duì)象

//打印結(jié)果如下:
objectClass = 0x7fff97528118 cls = 0x7fff97528118

每個(gè)類在內(nèi)存中有且只有一個(gè)class對(duì)象(類對(duì)象),通過打印內(nèi)存地址就可以看出來。
class對(duì)象在內(nèi)存中存儲(chǔ)的信息包括:
1.isa指針
2.superclass指針
3.類的屬性信息(@property),類的成員變量信息(ivar)
4.類的方法信息(method),類的協(xié)議信息(protocol)

class對(duì)象存儲(chǔ)信息.png

寫到這里有人就有疑問了,剛才不是說在instance對(duì)象中存儲(chǔ)成員變量信息嗎,怎么class對(duì)象中也存儲(chǔ)成員變量和屬性變量呢,這里要特意說明一點(diǎn):
成員變量的值時(shí)存儲(chǔ)在實(shí)例對(duì)象中的,因?yàn)橹挥挟?dāng)我們創(chuàng)建實(shí)例對(duì)象的時(shí)候才為成員變賦值。但是成員變量叫什么名字,是什么類型,只需要有一份就可以了。所以存儲(chǔ)在class對(duì)象中。
meta-class對(duì)象
只能是通過class對(duì)象獲取到meta-class對(duì)象,通過下面的方法獲取到。

//runtime中傳入類對(duì)象此時(shí)得到的就是元類對(duì)象
Class objectMetaClass = object_getClass([NSObject class]);
NSLog(@"objectMetaClass = %p",objectMetaClass);

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

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

//打印結(jié)果如下:
objectMetaClass 是否是元類對(duì)象 - 1

每一個(gè)類的meta-class對(duì)象在內(nèi)存中有且只有一個(gè),class對(duì)象跟meta-class對(duì)象結(jié)構(gòu)一樣,都是*struct objc_class Class,但是用途不一樣。
meta-class對(duì)象在內(nèi)存中存儲(chǔ)的信息包括:
1.isa指針
2.superclass指針
3.類的類方法信息(class-method)

meta-class存儲(chǔ)信息.png

既然class對(duì)象跟meta-class對(duì)象結(jié)構(gòu)一樣,那么class對(duì)象中是不是也有類方法信息呢?meta-class對(duì)象中是不是也有class對(duì)象中存儲(chǔ)的屬性信息,成員變量信息,方法信息,協(xié)議信息呢?答案是有的,只不過對(duì)應(yīng)的值可能是空的,所以忽略不計(jì)。

五.OC對(duì)象的isa指針指向問題

前面已經(jīng)說到了一個(gè)NSObject對(duì)象轉(zhuǎn)化為C++文件后,

struct NSObject_IMPL {
    Class isa;
};

所以說任何一個(gè)繼承NSObject的對(duì)象都包含一個(gè)isa指針,那么各個(gè)對(duì)象的isa指針又分別指向哪里呢?

我們先看兩個(gè)常用的調(diào)用方法

MJStudent *student = [[MJStudent alloc]init];
//方法1:調(diào)用實(shí)例方法
[student studentInstanceMethod];

方法1:student實(shí)例對(duì)象調(diào)用了實(shí)例方法,我們?cè)谇懊嬷v到過,實(shí)例方法信息存儲(chǔ)在class對(duì)象中,這時(shí)候instace對(duì)象中存儲(chǔ)的isa指針起到作用了,instace對(duì)象中的isa指針指向class對(duì)象,我們通過isa指針找到class對(duì)象,進(jìn)而找到實(shí)例方法列表,調(diào)用對(duì)應(yīng)方法。


對(duì)象方法調(diào)用軌跡.png
//方法2:調(diào)用類方法
[MJStudent studentClassMethod];

方法2:MJStudent類對(duì)象調(diào)用了類方法,我們?cè)谇懊嬷v到過,類方法信息存儲(chǔ)在meta-class對(duì)象中,這時(shí)候class對(duì)象中存儲(chǔ)的isa指針起到作用了,class對(duì)象中的isa指針指向meta-class對(duì)象,我們通過isa指針找到meta-class對(duì)象,進(jìn)而找到類方法列表,調(diào)用對(duì)應(yīng)方法。仿照上圖“對(duì)象方法調(diào)用軌跡.png”

總結(jié):instance對(duì)象——<isa指針>——class對(duì)象——<isa指針>——meta-class對(duì)象——<isa指針>——基類NSObject元類對(duì)象

詳細(xì)看下面isa指針圖例:

對(duì)象isa指針指向圖例.png

總結(jié)兩點(diǎn)比較坑的地方:

a.基類的元類對(duì)象的superclass指針指向基類的類對(duì)象
b.類的元類對(duì)象的isa指針指向基類的元類對(duì)象

六.OC對(duì)象的superclass指針指向位置

我們還是以Student類和Person類進(jìn)行說明,不清楚類信息的小伙伴請(qǐng)往前瀏覽一下。Student類是子類,Person類是父類。

//方法1:調(diào)用父類的實(shí)例方法
[student personInstanceMethod];
//方法2:調(diào)用父類的類方法
[MJStudent personClassMethod];

方法1:當(dāng)給student實(shí)例對(duì)象發(fā)送personInstanceMethod消息時(shí),student實(shí)例對(duì)象會(huì)通過isa指針找到對(duì)應(yīng)MJStudent類對(duì)象,因?yàn)轭悓?duì)象中存儲(chǔ)中對(duì)象方法信息,先從MJStudent類對(duì)象的實(shí)例方法信息中查找對(duì)應(yīng)的方法,如果找到進(jìn)行相應(yīng),沒找到則繼續(xù)向父類查找,那么子類怎么才能找到父類呢,這時(shí)候需要用到superclass指針了,通過superclass指針找到MJPerson的類對(duì)象,繼續(xù)從類對(duì)象那個(gè)的實(shí)例方法中查找,如果找到進(jìn)行相應(yīng),沒找到則繼續(xù)通過superclass查找基類NSObject類對(duì)象方法列表,如果還沒找到,返回nil,就是咱們常見的報(bào)錯(cuò)信息,找不到此方法。

方法2:跟方法1類似,簡單說一下吧
當(dāng)給MJStudent類對(duì)象發(fā)送personClassMethod消息時(shí),MJStudent類對(duì)象會(huì)通過isa指針找到對(duì)應(yīng)MJStudent元類對(duì)象,因?yàn)樵悓?duì)象中存儲(chǔ)中類方法信息,先從MJStudent元類對(duì)象的類信息中查找對(duì)應(yīng)的方法,如果找到進(jìn)行相應(yīng),沒找到則繼續(xù)向父類查找,那么子類怎么才能找到父類呢,這時(shí)候需要用到superclass指針了,通過superclass指針找到MJPerson的元類對(duì)象,繼續(xù)從元類對(duì)象那個(gè)的類方法中查找,如果找到進(jìn)行相應(yīng),沒找到則繼續(xù)通過superclass查找基類NSObject元類對(duì)象方法列表,如果還沒找到,這個(gè)時(shí)候跟方法1的查找不太一樣了,如果NSObject的元類對(duì)象的類方法中找到,就從NSObject的類方法的實(shí)例方法中去查找,還沒有找到,則返回nil,就是咱們常見的報(bào)錯(cuò)信息,找不到此方法。


子類調(diào)用父類方法查找順序.png

總結(jié):
1.子類類對(duì)象——<superclass指針>——父類類對(duì)象——<superclass指針>——基類類對(duì)象
2.子類元類對(duì)象——<superclass指針>——父類元類對(duì)象——<superclass指針>——基類元類對(duì)象——<superclass指針>基類的類對(duì)象

看完以上的解析,再來看這經(jīng)典的圖也不是那么的晦澀了


isa-superclass.png

對(duì)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)用對(duì)象方法的軌跡,isa找到class,方法不存在,就通過superclass找父類
class調(diào)用類方法的軌跡,isa找meta-class,方法不存在,就通過superclass找父類

七.代碼求證isa指針指向是否正確

我們先寫一段代碼:

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

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

我們通過命令行打印如下:

instance對(duì)象isa指針內(nèi)存地址.png

程序打印:類對(duì)象內(nèi)存地址 - 0x7fff9abb6118
命令行打?。簩?shí)例對(duì)象->isa指針獲取到的類對(duì)象內(nèi)存地址 - 0x001dffff9abb6119
怎么不一樣了呢?
原因是:這是因?yàn)閺?4bit開始,isa需要進(jìn)行一次位運(yùn)算,才能計(jì)算出真實(shí)地址。而位運(yùn)算的值我們可以通過下載objc源代碼找到。
arm64:表示真機(jī)應(yīng)用
x86_64:表示mac應(yīng)用
小碼哥視頻中創(chuàng)建的demo是mac應(yīng)用,所以用 0x00007ffffffffff8計(jì)算
ISA_MASK.png

我們按照這個(gè)原則再來操作一遍:
實(shí)例對(duì)象位運(yùn)算結(jié)果.png

果然跟程序打印出來的結(jié)果一樣,這足以證明上面總結(jié)的isa指針指向的正確性。

但我們?cè)俅螄L試驗(yàn)證類方法的isa指針指向的元類對(duì)象的內(nèi)存地址跟程序自然打印的是否一樣的時(shí)候,發(fā)現(xiàn)了如下問題


類對(duì)象isa指針內(nèi)存地址.png

小碼哥教給的做法是:為了拿到isa指針的地址,我們自己創(chuàng)建一個(gè)同樣的結(jié)構(gòu)體并通過強(qiáng)制轉(zhuǎn)化拿到isa指針。

struct mj_objc_class {
    Class isa;
    Class superclass;
};

struct mj_objc_class *nsobjectclass = (__bridge struct mj_objc_class *)([NSObject class]);

將OC中的類對(duì)象轉(zhuǎn)化成C語言的結(jié)構(gòu)體指針的時(shí)候需要進(jìn)行橋接,直接點(diǎn)fix就行


oc對(duì)象轉(zhuǎn)c語言結(jié)構(gòu)體變量.png
類對(duì)象isa指針內(nèi)存地址.png

程序打印:元類對(duì)象內(nèi)存地址 - 0x7fff9abb60f0
命令行打?。侯悓?duì)象->isa指針獲取到的元類類對(duì)象內(nèi)存地址 - 0x00007fff9abb60f0


類對(duì)象位運(yùn)算結(jié)果.png

總結(jié)一下本文面試題:

  • 1.面試題:一個(gè)NSObject對(duì)象占用多少內(nèi)存?

答:一個(gè)NSObject對(duì)象在內(nèi)存中占用多少內(nèi)存,其實(shí)就是isa結(jié)構(gòu)體類型的指針在內(nèi)存中占用的空間(64bit占8個(gè)字節(jié),32bit占4個(gè)字節(jié))

  • 2.面試題:自定義類的實(shí)例對(duì)象占用多少內(nèi)存?

答:根據(jù)情況而定,具體分析方法看上述說明

  • 3.面試題:繼承關(guān)系的子父類的實(shí)例對(duì)象占用多少內(nèi)存?

答:根據(jù)情況而定,具體分析方法看上述說明

  • 4.面試題:OC對(duì)象的類別有哪些呢?

instance對(duì)象(實(shí)例對(duì)象)
class對(duì)象(類對(duì)象)
meta-class對(duì)象(元類對(duì)象)

  • 5.面試題:OC對(duì)象的isa指針指向哪里呢?

答:instance對(duì)象的isa指針指向class對(duì)象,class對(duì)象的isa指針指向meta-class對(duì)象,meta-class對(duì)象的isa指針指向基類的meta-class對(duì)象,基類自己的isa指針也指向自己。

  • 6.面試題:OC的類信息存放在哪里?

instance對(duì)象(實(shí)例方法):存放isa指針,成員變量的具體數(shù)據(jù)
class對(duì)象(類對(duì)象):存放isa指針,superclass指針,類的成員變量(ivar),類的屬性信息(property),類的協(xié)議信息(protocol),類的方法列表(instance method list)
meta-class對(duì)象(元類對(duì)象:存放isa指針,superclass指針,類的方法列表(class method list)

本篇學(xué)習(xí)先記錄到此,感謝閱讀,如有錯(cuò)誤,不吝賜教,
非常感謝大佬帶飛:http://www.itdecent.cn/u/3171707d8892

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

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

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