1、Objective-C的本质
OC底层是由C\C++实现的,OC的面向对象是基于C\C++的数据结构实现的,OC的对象、类主要基于C\C++的结构体实现的,OC代码转换过程:
可以将OC代码转换成C\C++代码查看底层实现,参考:OC代码转换。
2、OC对象的本质
2.1、OC对象在内存中的布局
NSObject对象的底层实现是C++的结构体:
// OC代码
@interface NSObject {
Class isa;
}
// 底层实现
struct NSObject_IMPL {
Class isa;
};
结构体内部只有一个isa指针,32位系统下占用4个字节,64位系统占用8个字节,结构体所占内存大小就是isa指针的大小,结构体的内存地址就是isa指针的地址。
获取NSObject实例对象的大小:
// 调用class_getInstanceSize方法需要倒入头文件
#import <objc/runtime.h>
// 调用malloc_size方法需要倒入头文件
#import <malloc/malloc.h>
NSObject *obj = [[NSObject alloc] init];
// 获得NSObject实例对象的成员变量所占用的大小 -> 8
NSLog(@"obj = %zd", class_getInstanceSize([NSObject class]));
// 获得obj指针所指向内存的大小 -> 16
NSLog(@"obj = %zd", malloc_size((__bridge const void*)obj));
通过打印结果观察class_getInstanceSize方法方法获取obj对象占8个字节,通过malloc_size方法获取obj对象占用16个字节,为什么会读取出两个不同的大小?
分析 objc源码 可知class_getInstanceSize最终调用了alignedInstanceSize,获取的是成员变量的大小,即isa指针所占大小8:
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
分析 malloc源码 可知malloc_size最终调用了instanceSize,获取的是对象所占内存大小,最小为16个字节,所以得出16:
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
利用Xcode可以查看对象的内存布局,Debug - Debug Workflow - ViewMemory,输入NSObject对象地址:
可以看到,NSObject对象使用了前8个字节,后8个字节是空的,NSObject对象数据结构如下:
2.2、Student对象内存布局
创建一个继承于NSObject的类Student
@interface Student : NSObject {
@public
int _no;
int _age;
}
转成C++代码后:
struct NSObject_IMPL {
Class isa;
};
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _no;
int _age;
};
分别通过class_getInstanceSize和malloc_size读取大小,得到的都是16,原因是Student实例对象包含父类的isa指针和自己的成员变量,isa和成员变量所占内存大小是8 + 4 + 4 = 16,对象所占内存大小也是16。
结论:如果是继承关系,子类会包含父类的成员变量
使用View Memory查看Student对象的内存布局:
可以看到Student对象的使用了16个字节的内存,所以Student对象内存结构为:
2.3、内存对齐
创建一个Person类继承于NSObject,创建一个Student类继承于Person,分别有各自的属性
@interface Person : NSObject {
int _no;
}
@interface Student : Person {
int _age;
}
NSObject、Person、Student的底层结构如下:
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
};
struct Student_IMPL {
struct NSObject_IMPL Person_IVARS;
int _no;
};
Person和Student的内存结构为:
分别打印person和stu对象的class_getInstanceSize和malloc_size
NSLog(@"person = %zd",class_getInstanceSize([Person class]));
NSLog(@"person = %zd",malloc_size((__bridge const void*)person));
NSLog(@"stu = %zd",class_getInstanceSize([Student class]));
NSLog(@"stu = %zd",malloc_size((__bridge const void*)stu));
打印结果:
~:person = 16
~:person = 16
~:stu = 16
~:stu = 16
虽然person成员变量所占实际大小是12个字节,但是class_getInstanceSize实际上返回的是对齐后的内存大小,所以应该是8的倍数,返回的就是16。
*内存对齐:结构体的最终大小必须是最大成员大小的倍数
2.4、OC的内存对齐
创建一个Student类继承于NSObject
@interface Student : NSObject {
int _no;
int _age;
int _height;
}
分别获取class_getInstanceSize和malloc_size
NSLog(@"stu = %zd, %zd",class_getInstanceSize([Student class]),malloc_size((__bridge const void*)stu));
打印结果:
~: stu = 24, 32
分析:成员变量所占内存大小 8 + 4 + 4 + 4 = 20,根据内存对其规则,应该是8的倍数,所以结果是24,那么为什么malloc_size读取的是32?
分析 objc源码 和 malloc源码 得出:
class_getInstanceSize是获取创建这个实例对象至少需要多少内存,而malloc_size是获取创建这个实例对象实际上分配了多少内存,且OC内存对齐单位是16或者16的倍数,所以创建Student对象需要24个字节,但是实际上分配了32个字节。