OC对象内存大小及OC对象的分类

对小码哥底层班视频学习的总结与记录

OC对象内存大小及分配原理详解

我们开发中会自定义各种各样的类,基本上都是NSObject的子类。更为复杂的子类对象的内存布局又是如何的呢?我们新建一个NSObject的子类Student,并为其增加一些成员变量

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

@end

@implementation Student

@end

使用我们之前介绍过的方法,查看一下这个类的底层实现代码

struct NSObject_IMPL {
    Class isa;
};

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

我们发现其实Student的底层结构里,包含了它的成员变量,还有一个NSObject_IMPL结构体变量,也就是它的父类的结构体。根据我们上面的总结,NSObject_IMPL结构体需要的空间是8字节,但是系统给NSObject对象实际分配的内存是16字节,那么这里Student的底层结构体里面的成员变量NSObject_IMPL应该会得到多少的内存分配呢?我们验证一下。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        //获取`NSObject`类的实例对象的成员变量所占用的大小
        size_t size = class_getInstanceSize([NSObject class]);
        NSLog(@"NSObject实例对象的大小:%zd",size);
        //获取obj所指向的内存空间的大小
        size_t size2 = malloc_size((__bridge const void *)(obj));
        NSLog(@"对象obj所指向的的内存空间大小:%zd",size2);
        
        Student * std = [[Student alloc]init];
        size_t size3 = class_getInstanceSize([Student class]);
        NSLog(@"Student实例对象的大小:%zd",size3);
        size_t size4 = malloc_size((__bridge const void *)(std));
        NSLog(@"对象std所指向的的内存空间大小:%zd",size4);
    }
    return 0;
}
2021-04-06 10:33:20.696873+0800 Interview01-OC对象的本质[1653:40254] NSObject实例对象的大小:8
2021-04-06 10:33:20.697266+0800 Interview01-OC对象的本质[1653:40254] 对象obj所指向的的内存空间大小:16
2021-04-06 10:33:20.697311+0800 Interview01-OC对象的本质[1653:40254] Student实例对象的大小:16
2021-04-06 10:33:20.697344+0800 Interview01-OC对象的本质[1653:40254] 对象std所指向的的内存空间大小:16

从结果可以看出,Student类的底层结构体等同于

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

总结一下就是,一个子类的底层结构体,相当于 其父类结构体里面的所有成员变量 + 该子类自身定义的成员变量 所组成的一个结构体

多加了几个成员变量,验证我的猜想,创建一个Person类,如下:

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

转换后的底层代码:

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS; // 占8字节
    int _no;//占4字节
    int _age;//占4字节
    int _height;//占4字节
};
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;
}

可以看到,这个Person结构体所需要的内存是20个字节.下面我们使用sizeof,class_getInstanceSizemalloc_size打印一下看看结果:

2021-04-06 10:27:34.187462+0800 Interview01-OC对象的本质[1599:36288] 24
2021-04-06 10:27:34.187948+0800 Interview01-OC对象的本质[1599:36288] 24 32
image-20210406104222243
image-20210406104155588

可以看一下p在堆上的内存

image-20210406104331282

我们大致猜测一下,后面全0的都是在堆上开辟了空间给变量使用了,现在变量的长度为32!

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -0 main-arm64.cpp

生成的struct MJPerson_IMPL如下图所示:

image-20210406104935476

sizeof传入一个类型返回的是一个类型的大小,从上面底层代码中可以清晰的看到:Person_IMPL结构体只需要20个字节.但是我们通过sizeof获取的结果是24个字节,因为之前我们说过系统给结构体分配内存的原则是最大成员的倍数,而struct Person_IMPL的最大成员内存大小是8,所以分配 8 * 3 = 24 个字节.那为什么malloc_size打印的是==32个字节呢?==我们从runtime源码看一下.
步骤:

  • 1: 打开 runtime 源码,搜索 rootAllocWithZone点击进入该方法
  • 2: 点击进入 class_createInstance方法
  • 3: 点击进入 _class_createInstanceFromZone方法
  • 4: 点击进入 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;
    }

这个方法通过alignedInstanceSize() + extraBytes获取一个 size,而这个extraBytes一开始就通过class_createInstance(cls, 0)传入的就是0,所以 sieze 的大小就是alignedInstanceSize()返回的大小,而class_getInstanceSize内部也是调用alignedInstanceSize()方法:

size_t class_getInstanceSize(Class cls)
{
    if (!cls) return 0;
    return cls->alignedInstanceSize();
}

所以,alloc的底层其实就是:

    size_t instanceSize(size_t extraBytes) {
        size_t size = class_getInstanceSize() + 0;
        // CF requires all objects be at least 16 bytes.
        if (size < 16) size = 16;
        return size;
    }

class_getInstanceSize ()的结果是 24,为什么malloc_size输出的确是 32 呢?原因就在于_class_createInstanceFromZone()内部的calloc(1, size)方法,我们点击进入calloc(1, size)方法:

void    *calloc(size_t __count, size_t __size) __result_use_check __alloc_size(1,2);

到这里已经没法再看细节了,但是我们可以通过libmalloc库源码窥探一下allloc方法内存分配的原理.
从苹果官网下载libmalloc库后打开搜索malloc.c,然后找到calloc方法:

void *
calloc(size_t num_items, size_t size)
{
    void *retval;
        // 内存对齐,24 => 32
    retval = malloc_zone_calloc(default_zone, num_items, size);
    if (retval == NULL) {
        errno = ENOMEM;
    }
    return retval;
}

对比 runtime 源码中调用 calloc(1, size)方法传入的参数,参数 1 表示分配 1块内存,size就是就是上文分析的 24.传入的 24 结果变成了 32是因为在malloc_zone_calloc内部也进行了内存对齐,我们在调用alloc方法时系统内存对齐的规则如下:

#define NANO_MAX_SIZE           256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */

从系统层面来说,就以苹果系统而言,出于对内存管理和访问效率最优化的需要,会实现在内存中规划出很多块,这些块有大有小,但都是16的倍数,比如有的是32,有的是48,64

发现都是16的倍数,千万不要搞混淆了,之前说的结构体的对齐规则是结构体内最大成员所占字节的倍数,而alloc==16的倍数==.

sizeof,class_getInstanceSizemalloc_size这几个方法特别容易混淆,我们总结一下:

  • sizeof是运算符,编译的时候就替换为常数.返回的是一个类型所占内存的大小.
  • class_getInstanceSize传入一个类对象,返回一个对象的实例至少需要多少内存,它等价于sizeof.需要导入#import <objc/runtime.h>
  • malloc_size返回系统实际分配的内存大小,需要导入#import <malloc/malloc.h>

2个容易混淆的函数

image-20210406111142541

OC对象的分类

image-20210406111351728
  • instance对象(实例对象)
  • class对象(类对象)
  • meta-class对象(元类对象)

instance 实例对象

instance

instance对象就是通过alloc方法创建出来的对象,每次调用alloc方法都会生成新的instance对象

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

上面的object1 object2都是NSObject的instance对象(实例对象),因为他们都有自己的独有内存,所以是两个不同的对象

instance对象在内存中存放的信息包括

  • isa指针(因为基本上我们常用的类以及自定义类都继承自NSObject,所以我们这里讨论的instance里面都包含isa指针)
  • 其他成员变量

class 对象

class 对象

每个类在内存中有且只有一个类对象,他们在内存中存储的信息主要包括:
1: isa 指针
2: superclass 指针
3: 类的属性信息 (@property),类的对象方法信息 (instance method)
4: 类的协议信息 (@protocol),类的成员变量信息 (ivar)
获取一个对象的类信息,有三种方法:

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

@implementation MJPerson

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *object1 = [[NSObject alloc] init];
        NSObject *object2 = [[NSObject alloc] init];
        
        Class objectClass1 = [object1 class];
        Class objectClass2 = [object2 class];
        Class objectClass3 = object_getClass(object1);
        Class objectClass4 = object_getClass(object2);
        Class objectClass5 = [NSObject class];
        
        NSLog(@"%p %p",
              object1,
              object2);
        
        NSLog(@"%p %p %p %p %p",
              objectClass1,
              objectClass2,
              objectClass3,
              objectClass4,
              objectClass5);
    }
    return 0;
}
2021-04-06 11:21:36.973777+0800 Interview02-class对象[2084:65487] 0x10046e3d0 0x10046c890
2021-04-06 11:21:36.974230+0800 Interview02-class对象[2084:65487] 0x7fff90ebe118 0x7fff90ebe118 0x7fff90ebe118 0x7fff90ebe118 0x7fff90ebe118

一个类的类对象是唯一的,在内存中只存一份,这也很好理解,因为类对象中的信息只需要一份就够了。

meta-class 对象 (元类对象)

meta-class 对象

每个类中有且只有一个元类对象,同样通过object_getClass ()方法获得,只不过要把类对象当做参数传递进去.
meta-class 和 class 对象的内存结构是一样的,都是class类型,但是用途不一样,在内存中存储的信息主要包括:
1: isa 指针
2: superclass 指针
3: 类的类方法信息 (class method)
注意,meta-class 和 类对象的结构是一样的,也就是说 meta-class 对象中也有属性信息,协议信息,成员变量信息,实例方法信息,只不过在 meta-class 中,这些信息都是null.如图所示:

meta-class 对象

下面是元类对象一些相关的api:

  • object_getClass(<class对象>);获取元类对象,参数为一个类对象
  • class_isMetaClass(<class对象/meta-class对象>);判断是否是元类对象

因为classmeta-class对象都可以通过object_getClass获得,因此他们的类型都是一样的,都是typedef struct objc_class *Class这个Class类,可以理解,它们各自利用Class的一些成员变量来实现自己的功能。

class和meta-class

几个常用方法的区别

objc_getClass() , object_getClass() , class()方法区别:

  • object_getClass()
    可以通过 runtime 源码看一下object_getClass()的底层:
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

发现object_getClass()底层调用的是getIsa(),而实例对象的 isa 指针指向类对象,类对象的 isa 指针指向元类对象,元类对象的 isa 指针指向基类的元类对象.

结论:
1: 如果传入的是 instance 对象,返回 class 对象.
2: 如果传入 class 对象 返回 meta-class 对象.
3: 如果传入 meta-class 对象,返回 NSObject (基类)的 meta-class 对象.

  • objc_getClass()
    传入一个字符串类名,返回对应的类对象
  • class()
    返回类对象.
  NSObject *obj1 = [[NSObject alloc] init];
               
               Class objClass1 = [NSObject class];
               Class objClass2 = [obj1 class];
               Class objClass3 = [objClass1 class];
               Class objClass4 = [objClass2 class];
               //一个类在内存中只存一份
               NSLog(@"\nobjClass1:%p\nobjClass2:%p\nobjClass3:%p\nobjClass4:%p",
                     objClass1,
                     objClass2,
                     objClass3,
                     objClass4);
objClass1:0x7fff90ebe118
objClass2:0x7fff90ebe118
objClass3:0x7fff90ebe118
objClass4:0x7fff90ebe118

一个class对象调用(Class)class方法,返回的不是它的meta-class对象,而是它自身。总之,不论- (Class)class 还是 + (Class)class,都只能返回一个类的`class对象

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容