JHHK

欢迎来到我的个人网站
行者常至 为者常成

NSObject的本质(一) 占用内存

目录

NSObject对象占用多少内存

一、先来认识两个函数

可通过这两个函数来查看实例大小和占用空间的大小
获取实例大小

//获得NSObject实例对象的成员变量所占用的大小(理论需要的大小)
size_t size = class_getInstanceSize([obj class]);
printf("size=%zu\n",size);//8

获取开辟空间大小

//获得obj指针所指向内存的大小(实际分配的大小)
size_t size1 = malloc_size((__bridge const void *)obj);
printf("size=%zu\n",size1);//16

二、OC对象的本质

  • 我们平时编写的Objective-C代码,底层实现其实都是C\C++代码。
    OC的面向对象都是基于C\C++的结构体实现的。
    OC -> C\C++ -> 汇编 -> 机器语言
    为了探寻OC对象的本质,我们通过以下指令,将OC代码转为C\C++代码
    以 源文件名.cpp 输出
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件
    以指定的 文件名.cpp 输出
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
    指定runtime输出
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-9.0.0 main.m

  • main.m文件转为main.cpp文件

main.m文件

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject * obj = [[NSObject alloc] init];
    }
    return 0;
}

运行指令转为 main.cpp

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSObject * obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));

    }
    return 0;
}

去除类型转换

NSObject * obj = objc_msgSend(
                                objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc")),
                                sel_registerName("init")
                                );

三、OC对象转为结构体

main.m文件

typedef struct objc_class *Class;

@interface NSObject <NSObject> {
    Class isa;
}

@interface Person : NSObject{
    int _age;
}
@end

@interface Student : Person{
    int _number;
}
@end

运行指令转为 main.cpp文件

#ifndef _REWRITER_typedef_NSObject
#define _REWRITER_typedef_NSObject
typedef struct objc_object NSObject;
typedef struct {} _objc_exc_NSObject;
#endif

struct NSObject_IMPL {
	Class isa;
};


#ifndef _REWRITER_typedef_Person
#define _REWRITER_typedef_Person
typedef struct objc_object Person;
typedef struct {} _objc_exc_Person;
#endif

struct Person_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	int _age;
};

/* @end */


#ifndef _REWRITER_typedef_Student
#define _REWRITER_typedef_Student
typedef struct objc_object Student;
typedef struct {} _objc_exc_Student;
#endif

struct Student_IMPL {
	struct Person_IMPL Person_IVARS;
	int _number;
};

/* @end */

通过以上分析可知OC对象的地址,就是结构体的地址,就是结构体第一个成员变量的地址,就是isa的地址。

img

下面以代码的形式看下:

Student * student = [[Student alloc] init];
student->_age = 18;
student->_number = 20;
NSLog(@"student = %@",student);
//NSLog(@"student->isa=%p",student->isa);
NSLog(@"student->_age=%d",student->_age);
NSLog(@"student->_age=%d",student->_number);


//转换为相应的结构体类型
struct Student_IMPL * studentImpl = (__bridge struct Student_IMPL *)student;
NSLog(@"studentImpl = %p",studentImpl);
NSLog(@"studentImpl->isa=%p",studentImpl->isa);
NSLog(@"studentImpl->_age=%d",studentImpl->_age);
NSLog(@"studentImpl->_number=%d",studentImpl->_number);

NSLog(@"(*studentImpl).isa=%p",(*studentImpl).isa);
NSLog(@"(*studentImpl)._age=%d", (*studentImpl)._age);
NSLog(@"(*studentImpl)._number=%d", (*studentImpl)._number);


//打印结果:
/**
student = <Student: 0x10182be10>
student->_age=18
student->_age=20
    

studentImpl = 0x10182be10
studentImpl->isa=0x1d8001000013d9
studentImpl->_age=18
studentImpl->_number=20
(*studentImpl).isa=0x1d8001000013d9
(*studentImpl)._age=18
(*studentImpl)._number=20
*/


//lldb的调试结果
/**
(lldb) p student
(Student *) $0 = 0x000000010182be10       
(lldb) p &student->isa
(__unsafe_unretained Class *) $1 = 0x000000010182be10      
(lldb) p &student->_age
(int *) $2 = 0x000000010182be18      
(lldb) p &student->_number
(int *) $3 = 0x000000010182be1c


(lldb) p studentImpl
(Student_IMPL *) $4 = 0x000000010182be10
(lldb) p &studentImpl->isa
(Class *) $5 = 0x000000010182be10
(lldb) p &studentImpl->_age
(int *) $6 = 0x000000010182be18
(lldb) p &studentImpl->_number
(int *) $7 = 0x000000010182be1c
*/

通过以上分析,可知Student类的实例,与结构体Student_IMPL类型是一一对应的关系。

四、内存大小

//创建一个NSObject对象
NSObject * obj = [[NSObject alloc] init];
size_t objInSize = class_getInstanceSize([obj class]);
NSLog(@"objInSize=%zu",objInSize);
size_t objMaSize = malloc_size((__bridge const void *)obj);
NSLog(@"objMaSize=%zu",objMaSize);


//创建一个Person对象
Person * person = [[Person alloc] init];
person->_age = 18;
size_t perInSize = class_getInstanceSize([Person class]);
NSLog(@"perInSize=%zu",perInSize);
size_t perMaSize = malloc_size((__bridge const void *)person);
NSLog(@"perMaSize=%zu",perMaSize);


//创建一个Student对象
Student * student = [[Student alloc] init];
student->_age = 18;
student->_number = 20;
size_t stuInSize = class_getInstanceSize([Student class]);
NSLog(@"stuInSize = %zu",stuInSize);
size_t stuMaSize = malloc_size((__bridge const void *)student);
NSLog(@"stuMaSize = %zu",stuMaSize);

//打印结果为
/**
objInSize=8
objMaSize=16
perInSize=16
perMaSize=16
stuInSize = 16
stuMaSize = 16
*/

首先明白一个问题,内存分配以字节Byte为单位,OC对象实际分配的空间是16Byte的倍数。

objInsize=8:obj对象只有一个isa指针,所以实际的大小是8字节。
objMaSize=16:OC对象实际分配空间应是16倍数,所以为16。

perInSize=16:person对象一个isa指针8个字节,一个int类型4个字节,共需12字节,那么为什么是16字节呢?这里就涉及到结构体的内存对齐问题了。
关于C语言结构体的内存对齐问题请参考文章 C语言结构体
perMaSize=16:OC对象实际分配空间应是16倍数,所以为16。

stuInSize=16:isa8个字节,age4个字节,number4个字节,所以实际大小是16字节。
stuMaSize=16:OC对象实际分配空间应是16倍数,所以为16。


行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.