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

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

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

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

好了沉迹,帶著問(wèn)題,我們一一開(kāi)始閱讀吧 ??

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

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

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

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

OC代碼如下:

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

我們要想看到OC代碼轉(zhuǎn)化為C++文件,需要通過(guò)命令行進(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

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

struct NSObject_IMPL {
    Class isa;
};

發(fā)現(xiàn)里面只有一個(gè)Class類(lèi)型的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;

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

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

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

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

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

@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類(lèi)轉(zhuǎn)化為C++的結(jié)構(gòu)體后第一項(xiàng)是struct NSObject_IMPL 佣蓉,前面探討NSObject對(duì)象的時(shí)候?qū)戇^(guò)

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類(lèi)型_no4個(gè)字節(jié)空間+int類(lèi)型_age4個(gè)字節(jié)空間共16個(gè)字節(jié)空間

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

 NSLog(@"NSObject = %zd",class_getInstanceSize([NSObject class]));
//類(lèi)對(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ù),那怎么做呢虾标?
方式一:通過(guò)打斷點(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ù)開(kāi)始讀傀蚌,查看前16位字節(jié),每四個(gè)字節(jié)讀出的數(shù)據(jù)為
16進(jìn)制 0x0000004(4字節(jié)) 0x0000005(4字節(jié)) isa的地址為 00D1081000001119(8字節(jié))

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

memory read 0x10074c450
// 簡(jiǎn)寫(xiě)  x 0x10074c450

// 增加讀取條件
// memory read/數(shù)量格式字節(jié)數(shù)  內(nèi)存地址
// 簡(jiǎn)寫(xiě) 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í)也可以通過(guò)lldb修改內(nèi)存中的值

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

3.面試題:在64bit環(huán)境下,繼承關(guān)系的子父類(lèi)占用內(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í)這道題主要考察的面試題是什么呢雨膨?繼承類(lèi)的內(nèi)存大小如何計(jì)算呢?
我們依次將上面的Student子類(lèi)跟Person父類(lèi)轉(zhuǎn)化成C++結(jié)構(gòu)體寫(xiě)出來(lái)

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ì)疑問(wèn)了Person_IMPL 不是占用12個(gè)字節(jié)嗎读串,怎么顯示16呢聊记?那是因?yàn)橄到y(tǒng)給對(duì)象分配內(nèi)存時(shí)會(huì)遵循內(nèi)存對(duì)齊:結(jié)構(gòu)體的大小必須是最大成員大小的倍原則,也就說(shuō)Person_IMPL結(jié)構(gòu)體中的成員變量(isa_age)實(shí)際需要12字節(jié)空間恢暖,但是系統(tǒng)根據(jù)原則確分配了16字節(jié)排监,所以結(jié)果是16字節(jié)。
而** Student_IMPL怎么又成了16字節(jié)呢杰捂,上面說(shuō)了系統(tǒng)給Person_IMPL分配了16字節(jié)舆床,實(shí)際占用12字節(jié),還留有4字節(jié)空余,恰好放_no**4字節(jié)的變量挨队,這樣出來(lái)的結(jié)果就是系統(tǒng)分配16字節(jié)恰好夠Student_IMPL對(duì)象使用谷暮。

敲黑板了!J⒖选湿弦!

所以,綜上腾夯,我們總結(jié)一下系統(tǒng)給對(duì)象分配存儲(chǔ)空間的原則:編譯器在給結(jié)構(gòu)體開(kāi)辟空間時(shí)颊埃,首先找到結(jié)構(gòu)體中最寬的基本數(shù)據(jù)類(lèi)型,然后尋找內(nèi)存地址能是該基本數(shù)據(jù)類(lèi)型的整倍的位置俯在,作為結(jié)構(gòu)體的首地址竟秫。將這個(gè)最寬的基本數(shù)據(jù)類(lèi)型的大小作為對(duì)齊模數(shù)。
為結(jié)構(gòu)體的一個(gè)成員開(kāi)辟空間之前跷乐,編譯器首先檢查預(yù)開(kāi)辟空間的首地址相對(duì)于結(jié)構(gòu)體首地址的偏移是否是本成員的整數(shù)倍肥败,若是,則存放本成員愕提,反之馒稍,則在本成員和上一個(gè)成員之間填充一定的字節(jié),以達(dá)到整數(shù)倍的要求浅侨,也就是將預(yù)開(kāi)辟空間的首地址后移幾個(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ì)象的類(lèi)別以及存儲(chǔ)信息

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

先來(lái)一段代碼

#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ì)象類(lèi)分為幾類(lèi)呢澳化?

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

instance對(duì)象
通過(guò)類(lèi)alloc 出來(lái)的對(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é)果就能看出來(lái)灶似,它們分別占據(jù)不同的內(nèi)存地址列林。
instance對(duì)象在內(nèi)存中存儲(chǔ)的信息包括:
1.isa指針
2.成員變量具體的數(shù)據(jù)

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

class對(duì)象
我們通過(guò)class方法或者runtime方法得到一個(gè)class對(duì)象,class對(duì)象也就是類(lèi)對(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)用類(lèi)對(duì)象的class方法時(shí)得到還是類(lèi)對(duì)象酪惭,無(wú)論調(diào)用多少次都是類(lèi)對(duì)象
Class cls = [[NSObject class] class];
Class objectClass6 = [NSObject class];
NSLog(@"objectClass = %p cls = %p", objectClass6, cls); // 后面兩個(gè)地址相同希痴,說(shuō)明多次調(diào)用class得到的還是類(lèi)對(duì)象

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

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

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

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

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

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

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

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

每一個(gè)類(lèi)的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.類(lèi)的類(lèi)方法信息(class-method)

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

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

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

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

struct NSObject_IMPL {
    Class isa;
};

所以說(shuō)任何一個(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到過(guò)用押,實(shí)例方法信息存儲(chǔ)在class對(duì)象中,這時(shí)候instace對(duì)象中存儲(chǔ)的isa指針起到作用了靶剑,instace對(duì)象中的isa指針指向class對(duì)象蜻拨,我們通過(guò)isa指針找到class對(duì)象,進(jìn)而找到實(shí)例方法列表桩引,調(diào)用對(duì)應(yīng)方法缎讼。


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

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

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

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

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

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

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

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

我們還是以Student類(lèi)和Person類(lèi)進(jìn)行說(shuō)明舰讹,不清楚類(lèi)信息的小伙伴請(qǐng)往前瀏覽一下茅姜。Student類(lèi)是子類(lèi),Person類(lèi)是父類(lèi)月匣。

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

方法1:當(dāng)給student實(shí)例對(duì)象發(fā)送personInstanceMethod消息時(shí)钻洒,student實(shí)例對(duì)象會(huì)通過(guò)isa指針找到對(duì)應(yīng)MJStudent類(lèi)對(duì)象,因?yàn)轭?lèi)對(duì)象中存儲(chǔ)中對(duì)象方法信息锄开,先從MJStudent類(lèi)對(duì)象的實(shí)例方法信息中查找對(duì)應(yīng)的方法素标,如果找到進(jìn)行相應(yīng),沒(méi)找到則繼續(xù)向父類(lèi)查找萍悴,那么子類(lèi)怎么才能找到父類(lèi)呢头遭,這時(shí)候需要用到superclass指針了,通過(guò)superclass指針找到MJPerson的類(lèi)對(duì)象退腥,繼續(xù)從類(lèi)對(duì)象那個(gè)的實(shí)例方法中查找任岸,如果找到進(jìn)行相應(yīng),沒(méi)找到則繼續(xù)通過(guò)superclass查找基類(lèi)NSObject類(lèi)對(duì)象方法列表狡刘,如果還沒(méi)找到享潜,返回nil,就是咱們常見(jiàn)的報(bào)錯(cuò)信息嗅蔬,找不到此方法剑按。

方法2:跟方法1類(lèi)似,簡(jiǎn)單說(shuō)一下吧
當(dāng)給MJStudent類(lèi)對(duì)象發(fā)送personClassMethod消息時(shí)澜术,MJStudent類(lèi)對(duì)象會(huì)通過(guò)isa指針找到對(duì)應(yīng)MJStudent元類(lèi)對(duì)象艺蝴,因?yàn)樵?lèi)對(duì)象中存儲(chǔ)中類(lèi)方法信息,先從MJStudent元類(lèi)對(duì)象的類(lèi)信息中查找對(duì)應(yīng)的方法鸟废,如果找到進(jìn)行相應(yīng)猜敢,沒(méi)找到則繼續(xù)向父類(lèi)查找,那么子類(lèi)怎么才能找到父類(lèi)呢盒延,這時(shí)候需要用到superclass指針了缩擂,通過(guò)superclass指針找到MJPerson的元類(lèi)對(duì)象,繼續(xù)從元類(lèi)對(duì)象那個(gè)的類(lèi)方法中查找添寺,如果找到進(jìn)行相應(yīng)胯盯,沒(méi)找到則繼續(xù)通過(guò)superclass查找基類(lèi)NSObject元類(lèi)對(duì)象方法列表,如果還沒(méi)找到计露,這個(gè)時(shí)候跟方法1的查找不太一樣了博脑,如果NSObject的元類(lèi)對(duì)象的類(lèi)方法中找到憎乙,就從NSObject的類(lèi)方法的實(shí)例方法中去查找,還沒(méi)有找到叉趣,則返回nil泞边,就是咱們常見(jiàn)的報(bào)錯(cuò)信息,找不到此方法。


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

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

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


isa-superclass.png

對(duì)isa值漫、superclass總結(jié)
instance的isa指向class
class的isa指向meta-class
meta-class的isa指向基類(lèi)的meta-class,基類(lèi)的isa指向自己
class的superclass指向父類(lèi)的class椭蹄,如果沒(méi)有父類(lèi),superclass指針為nil
meta-class的superclass指向父類(lèi)的meta-class净赴,基類(lèi)的meta-class的superclass指向基類(lèi)的class
instance調(diào)用對(duì)象方法的軌跡绳矩,isa找到class,方法不存在玖翅,就通過(guò)superclass找父類(lèi)
class調(diào)用類(lèi)方法的軌跡翼馆,isa找meta-class,方法不存在金度,就通過(guò)superclass找父類(lèi)

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

我們先寫(xiě)一段代碼:

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

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

我們通過(guò)命令行打印如下:

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

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

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

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

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


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

小碼哥教給的做法是:為了拿到isa指針的地址勘高,我們自己創(chuàng)建一個(gè)同樣的結(jié)構(gòu)體并通過(guò)強(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中的類(lèi)對(duì)象轉(zhuǎn)化成C語(yǔ)言的結(jié)構(gòu)體指針的時(shí)候需要進(jìn)行橋接,直接點(diǎn)fix就行


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

程序打踊:元類(lèi)對(duì)象內(nèi)存地址 - 0x7fff9abb60f0
命令行打硬阋凇:類(lèi)對(duì)象->isa指針獲取到的元類(lèi)類(lèi)對(duì)象內(nèi)存地址 - 0x00007fff9abb60f0


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

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

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

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

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

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

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

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

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

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

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

答:instance對(duì)象的isa指針指向class對(duì)象,class對(duì)象的isa指針指向meta-class對(duì)象劲腿,meta-class對(duì)象的isa指針指向基類(lèi)的meta-class對(duì)象旭绒,基類(lèi)自己的isa指針也指向自己。

  • 6.面試題:OC的類(lèi)信息存放在哪里焦人?

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末批狐,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碳默,更是在濱河造成了極大的恐慌贾陷,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嘱根,死亡現(xiàn)場(chǎng)離奇詭異髓废,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)该抒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)慌洪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人凑保,你說(shuō)我怎么就攤上這事冈爹。” “怎么了欧引?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵频伤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我芝此,道長(zhǎng)憋肖,這世上最難降的妖魔是什么因痛? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮岸更,結(jié)果婚禮上鸵膏,老公的妹妹穿的比我還像新娘。我一直安慰自己怎炊,他們只是感情好谭企,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著评肆,像睡著了一般债查。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糟港,一...
    開(kāi)封第一講書(shū)人閱讀 51,287評(píng)論 1 301
  • 那天攀操,我揣著相機(jī)與錄音,去河邊找鬼秸抚。 笑死速和,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的剥汤。 我是一名探鬼主播颠放,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼吭敢!你這毒婦竟也來(lái)了碰凶?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤鹿驼,失蹤者是張志新(化名)和其女友劉穎欲低,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體畜晰,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡砾莱,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了凄鼻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腊瑟。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖块蚌,靈堂內(nèi)的尸體忽然破棺而出闰非,到底是詐尸還是另有隱情,我是刑警寧澤峭范,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布财松,位于F島的核電站,受9級(jí)特大地震影響纱控,放射性物質(zhì)發(fā)生泄漏辆毡。R本人自食惡果不足惜政敢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胚迫。 院中可真熱鬧,春花似錦唾那、人聲如沸访锻。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)期犬。三九已至,卻和暖如春避诽,著一層夾襖步出監(jiān)牢的瞬間龟虎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工沙庐, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鲤妥,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓拱雏,卻偏偏與公主長(zhǎng)得像棉安,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子铸抑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容