阅读 144

Objective-C的本质之NSObject对象占用多少内存?

我们通常认为:在C语言中 int占用4个字节,char占用1个字节......其实C标准并没有具体给出规定哪个基本类型应该是多少个字节数,而且这也与OS、编译器相关。

下面给出一个表格,方便查看各类型的字节数:

image.png

上图只是简单的基本数据类型的字节数:

在现实生活中,我们可以用 int类型 来表示有几个????。可以用 char类型表示'a'字符。可以用float来表示一个身高1.75米等。

当我们要表示一个数据集合的时候,我们可以用 int数组表示,一个班级中所有学生的数学成绩。

进而我们可能会用一个结构体struct,来表示我们的学校:学校的名字,学校地址。。。。。。

这里提到的结构体struct,和C语言的精髓,指针,在一起,可以很愉快的玩耍,比如可以形成链表......研究Objective-C的本质,也需要你了解struct和指针的相关知识。

进而.......

目前我们接触的许多编程语言都是类C的语言。Objective-C更是基于C/C++开发的.

事实上:我们平时所写的Objective-C代码,底层实现都是C/C++代码。

Objective-C的面向对象都是基于C/C++的数据结构实现的。

将OC代码转换成C/C++代码:

将main.c  转化为 main-arm64.cpp的指令如下:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp复制代码

我们发现,NSObject.h 头文件中, 有一个Class isa的成员变量

@interface NSObject <NSObject> {

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wobjc-interface-ivars"

    Class isa  OBJC_ISA_AVAILABILITY;

#pragma clang diagnostic pop

}复制代码

在 main-arm64.cpp 文件中:

image.png

typedef struct objc_class *Class;复制代码

对比之后:可以这么说。

@interface NSObject{

    Class isa  ;

}

@end复制代码

底层被转换成了:

struct NSObject_IMPL {

Class isa;

};复制代码

由于:Class 的类型是指针:

typedef struct objc_class *Class;复制代码

在苹果arm64架构上,指针是 占用8个字节。

我们可以说 一个NSObject占用8个字节吗? 其实并不是8个字节。

 NSObject *obj = [[NSObject alloc] init];

 // 获得NSObject实例对象的成员变量所占用的大小 >> 8\


 NSLog(@"%zd", class_getInstanceSize([NSObject class]));  

 // 获得obj指针所指向内存的大小 >> 16

 NSLog(@"%zd", malloc_size((__bridge const void *)obj));复制代码

通过class_getInstanceSize()  // 获得 NSObject实例对象的成员变量所占用的大小。

通过 malloc_size()  //获得obj指针所指向内存的大小。

通过测试发现:class_getInstanceSize() 获得的是 8个字节。 malloc_size() 获得的是16个字节。

 NSObject *obj = [[NSObject alloc] init];复制代码

上述代码:产生了一个obj对象。他的内存是如何布局的?

image.png

由于isa是指针类型。按道理说obj 所占用得 8个字节的内存。

让我们进入到函数调用的内部一查究竟。

alloc函数的实现,我们可以在苹果开放的源码中查找

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;

    }
    
    // CF requires all objects be at least 16 bytes.复制代码

如果内存大小 < 16字节,都会给开辟16字节的空间大小。

在Xcode中找到 Debug --> Debug Workflow --> view Memory 查看内存:

image.jpeg

其实我们通过Xcode 查看内存,也可以发现,确实是16个字节。只不过Class isa 成员变量,只使用了8个字节。但是内存确实是分配了16个字节。

所以:一个NSObject对象占用多少内存?

系统分配了16个字节给NSObject对象。但NSObject对象内部只使用了8个字节的空间(64bit环境下)可以通过class_getinstance函数获得。

接下来,看一下Student对象在内存中占用几个字节?

@interface Student : NSObject

{

    @public

    int _no;

    int _age;

}

@end

@implementation Student

@end复制代码

将OC代码转为C/C++代码后,会发现

struct Student_IMPL {

    Class isa;

    int _no;

    int _age;

};复制代码
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));复制代码

通过测试发现:class_getInstanceSize() 获得的是 16个字节。 malloc_size() 获得的是16个字节。

通过Xcode工具查看内存:确实也是16个字节。

image.jpeg

可以看到内存中的04 00 00 00 代表 4,即stu->_no, 内存中的05 00 00 00 代表 5,即stu->_age。

注意:04 00 00 00 为什么代表 4 , 请自己了解 大小端。 苹果arm64是小端。 大端模式定义是:大端模式就是低位存放在高地址上。高位存放在低地址上。

下面,我们看一下,更复杂的继承结构:

@interface Person : NSObject

{
    @public
    int _age;
}

@end


@implementation Person


@end


@interface Student : Person

{
    int _no;
}

@end


@implementation Student


@end复制代码

将OC代码转化为C/C++代码:

struct NSObject_IMPL {

    Class isa;

};

struct Person_IMPL {

    struct NSObject_IMPL NSObject_IVARS; // 8

    int _age; // 4

}; // 16 内存对齐规则之一:结构体的大小必须是最大成员大小的倍数



struct Student_IMPL {

    struct Person_IMPL Person_IVARS;

    int _no; 

}; // 16复制代码

通过测试发现:class_getInstanceSize() 获得的是 16个字节。 malloc_size() 获得的是16个字节。

通过XCode查看内存,确实各占用16字节的内存。其中 Person对象占用16字节,是因为12<16, 所以实际应分配16字节的内存。前面已经讲述。  Student对象占用16字节是因为8+4+4 = 16。

前面我们讲了对象的成员变量所占内存。接下来我们看一下属性变量。

我们常说:OC中的属性会自动增加一个带_的成员变量。get及set方法。

正好我们从内存本质来看一下。会不会自动生成成员变量。

@interface Person : NSObject

{

    @public

    int _age;

}

@property (nonatomic, assign) int height;

@end

@implementation Person


@end


OC转换成C/C++后:

struct Person_IMPL {

struct NSObject_IMPL NSObject_IVARS;

int _age;

int _height;

};复制代码

我们发现,确实自动生成了_height,很好的证明了OC语法。

那么setHeight getHeight方法去哪里了?我们alloc出来的类实例对象只存在成员变量。

方法是不可能放在成员变量里的。

因为方法有一份就够了。

我们再使用Person类创建Person对象的时候,一般会创建多个对象。

方法不可能在每个实例里都有。只有成员变量,每个Person对象都会有一份。

其实,方法不可能存在成员变量里,只放在类对象方法列表里。

以后我们再来讨论。。。

重要:

如果你在日常的研究中发现。对象的内存占用和你的预期产生了出入,不要着急。以下进行说明。

创建对象时为对象开辟内存时调用 alloc方法 最终会调用calloc内存开辟函数。

在libmalloc库中,(苹果开源的代码中可以下载该库)你找到该库,找到这样一个宏定义。

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

苹果在分配内存的时候,有内存对齐,这个内存对齐和结构体的内存对齐是两码事。

其实,对象内存都是16字节的倍数。 

所以如果你看到你发现你预期的内存应该是24啊。怎么变成了32? 不要惊讶,苹果的iOS系统在给你做内存分配的时候,是有一定规则的,这有利于内存的管理。


作者:用户2109385917629
链接:https://juejin.cn/post/7020611634368348168


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐