目录
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的地址。
下面以代码的形式看下:
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。
行者常至,为者常成!