iOS程序猿IOS

[CH1-Q2]对象的本质——一个自定义实例对象占用多少内存?

2018-12-22  本文已影响46人  鲸鱼座的男孩

上一节(CH1-Q1)我们谈到一个普通的NSObject实例对象所占用的内存空间。NSObject对象内部虽然只使用了8个字节(isa指针),但系统分配了16个字节给NSObject 实例对象,因此一个NSObject实例对象实际所占用的内存空间为16个字节。

但在我们日常的开发中,我们不仅仅使用NSObject这个对象,我们还会自定义许多自己业务相关的对象,那么接下来我们一起去了解一下一个自定义的实例对象在内存中又是如何去分布的?它又占用多少的内存空间?

创建一个MacOS命令行程序,声明一个Student类:

    @interface Student : NSObject {
        @public
        int _no;
        int _age;
    }
    
    @implementation Student
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Student *stu = [[Student alloc] init];
        }
        return 0;
    }

*Q2:一个Student实例对象占用多少内存空间?*

这个问题跟我们上一节问的一个NSObject实例对象占用多少内存空间?其实是类似的,我们也可以把这个问题转换成: stu指针指向的内存空间占多大?

我们知道每创建一个Student实例对象,该实例对象都有属于自己的成员变量_no_age,而它们的类型都是int类型,我们知道一个int类型的成员变量在64bit机器上所占用的内存空间是4个字节,因此_no_age成员变量一共占用8个字节。那么,是否一个Student实例对象就只占用8个字节呢?答案是否定的,因为我们可以观察到 Student : NSObject ,Student这个类是继承于NSObject这个类的,一个NSObject实例对象都占用了16位的内存空间,那么身为它子类的实例对象,不可能比NSObject实例对象所占用的内存空间少。

既然继承NSObject,那么NSObjectisa成员变量应该在Student实例对象中也存在。那么我们如何去验证呢?我们可以通过终端的方式,生成C++代码去观察。
在终端输入:

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

通过搜索: struct Student_IMPL,我们能够得到以下的结构体:

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

可以发现Student_IMPL结构体中包含一个NSObject_IMPL结构体的成员变量,对于NSObject_IMPL我们已经很熟悉了,其实它就是以下的代码:

    struct NSObject_IMPL {
        Class isa;
    };

结合上面的两个结构体,我们能够得出:

     struct Student_IMPL {
            Class isa;   // 8个字节    
            int _no;        // 4个字节
            int _age;       // 4个字节
    };
15454041174968.jpg

我们可以通过:把指向Student实例对象的指针stu强制转换成struct Student_IMPL *结构体类型的指针stuImpl,同时我们可以使用stuImpl指针获取_no_age的值,如果输出的值是跟我们再前面给Student实例对象赋值一样的话,我们可以证明,我们上图的Student实例对象内存分配是正确的。

struct Student_IMPL {
    Class isa; // 8个字节
    int _no;   // 4个字节
    int _age;  // 4个字节
};

@interface Student : NSObject
{
    @public
    int _no; //4个字节
    int _age;
}
@end

@implementation Student


@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        //stu指针存储的其实就是 isa的地址值
        Student *stu = [[Student alloc] init];
        stu->_no = 7;
        stu->_age = 8;
        
        // 把stu 转成stuImpl类型
        struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
        NSLog(@"no:%d", stuImpl->_no);
        NSLog(@"no:%d", stuImpl->_age);
    }
    return 0;
}

当然我们也可以用另外一个角度去证明:


15454048403144.jpg

获取Student实例对象的地址(0x0000000102120390),我们可以通过(View Memory)这个工具来帮我们一探究竟。

15454049377713.jpg 15454052395831.jpg

最后我们还可以通过上一节用到<malloc/malloc.h>中的malloc_size<objc/runtime.h>中的class_getInstanceSize两个方法去得到Student实例对象实际分配的内存大小和所用到的内存大小。

15454055526717.jpg

可以看到两个方法所输出的值都为16,那么我们可以说该Student类的实例对象占用的内存空间为16个字节,而且它实际使用到的内存空间也为16个字节。

如果我们再在Student类中加入一个double类型的成员变量height,那么其结构体就可以转换成:

     struct Student_IMPL {
            Class isa;
            int _no;        // 4个字节
            int _age;       // 4个字节
            double _height; // 8个字节
    };
15454061417297.jpg

我们可能又有疑问,为什么开辟的内存空间大小是32呢?其实这里就涉及到一个内存对其的概念,通过我的归纳,可以总结为:

  1. malloc_size()为类对象动态分配的内存大小,是补齐之后的大小,即如果一个类对象需要的内存大小为20,通过malloc_size()方法获取的大小为32,即16的倍数;

  2. class_getInstanceSize()方法获取的内存大小也是补齐之后的大小,补齐规则按照类的成员变量所占内存最大值的整数倍

对照上面的Student类就是:

  1. isa_no_age_height属性一共占用24个字节,但是实际需要分配的大小需要为16的倍数,因此最低要开辟32个字节的内存空间。

  2. isa_no_age_height属性一共占用24个字节,是类的成员变量isa_height所占内存的最大值的整数倍,因此Student类的属性使用了24个字节的内存空间。

总结:通过上一节(CH1-Q1)和本节的内容,相信大家对NSObject对象和我们自定义的对象它们的实例对象在内存中占用的内存空间究竟是多少有了一定了了解。另外下一节的内容我们会继续探讨OC对象的本质:包括OC对象的方法又是存在内存哪里的?alloc和size的分析等内容。

如果对本节内容有什么疑问?可以添加下面的微信跟我一起讨论。

1791545122023_.pic.jpg
上一篇 下一篇

猜你喜欢

热点阅读