1、Objective-C的本质

OC底层是由C\C++实现的,OC的面向对象是基于C\C++的数据结构实现的,OC的对象、类主要基于C\C++的结构体实现的,OC代码转换过程:
image.png
可以将OC代码转换成C\C++代码查看底层实现,参考:OC代码转换

2、OC对象的本质

2.1、OC对象在内存中的布局

NSObject对象的底层实现是C++的结构体:

  1. // OC代码
  2. @interface NSObject {
  3. Class isa;
  4. }
  5. // 底层实现
  6. struct NSObject_IMPL {
  7. Class isa;
  8. };

结构体内部只有一个isa指针,32位系统下占用4个字节,64位系统占用8个字节,结构体所占内存大小就是isa指针的大小,结构体的内存地址就是isa指针的地址。
获取NSObject实例对象的大小:

  1. // 调用class_getInstanceSize方法需要倒入头文件
  2. #import <objc/runtime.h>
  3. // 调用malloc_size方法需要倒入头文件
  4. #import <malloc/malloc.h>
  5. NSObject *obj = [[NSObject alloc] init];
  6. // 获得NSObject实例对象的成员变量所占用的大小 -> 8
  7. NSLog(@"obj = %zd", class_getInstanceSize([NSObject class]));
  8. // 获得obj指针所指向内存的大小 -> 16
  9. NSLog(@"obj = %zd", malloc_size((__bridge const void*)obj));

通过打印结果观察class_getInstanceSize方法方法获取obj对象占8个字节,通过malloc_size方法获取obj对象占用16个字节,为什么会读取出两个不同的大小?
分析 objc源码 可知class_getInstanceSize最终调用了alignedInstanceSize,获取的是成员变量的大小,即isa指针所占大小8:

  1. // Class's ivar size rounded up to a pointer-size boundary.
  2. uint32_t alignedInstanceSize() const {
  3. return word_align(unalignedInstanceSize());
  4. }

分析 malloc源码 可知malloc_size最终调用了instanceSize,获取的是对象所占内存大小,最小为16个字节,所以得出16:

  1. inline size_t instanceSize(size_t extraBytes) const {
  2. if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
  3. return cache.fastInstanceSize(extraBytes);
  4. }
  5. size_t size = alignedInstanceSize() + extraBytes;
  6. // CF requires all objects be at least 16 bytes.
  7. if (size < 16) size = 16;
  8. return size;
  9. }

利用Xcode可以查看对象的内存布局,Debug - Debug Workflow - ViewMemory,输入NSObject对象地址:
image.png
可以看到,NSObject对象使用了前8个字节,后8个字节是空的,NSObject对象数据结构如下:
image.png

2.2、Student对象内存布局

创建一个继承于NSObject的类Student

  1. @interface Student : NSObject {
  2. @public
  3. int _no;
  4. int _age;
  5. }

转成C++代码后:

  1. struct NSObject_IMPL {
  2. Class isa;
  3. };
  4. struct Student_IMPL {
  5. struct NSObject_IMPL NSObject_IVARS;
  6. int _no;
  7. int _age;
  8. };

分别通过class_getInstanceSize和malloc_size读取大小,得到的都是16,原因是Student实例对象包含父类的isa指针和自己的成员变量,isa和成员变量所占内存大小是8 + 4 + 4 = 16,对象所占内存大小也是16。
结论:如果是继承关系,子类会包含父类的成员变量
使用View Memory查看Student对象的内存布局:
image.png
可以看到Student对象的使用了16个字节的内存,所以Student对象内存结构为:
image.png

2.3、内存对齐

创建一个Person类继承于NSObject,创建一个Student类继承于Person,分别有各自的属性

  1. @interface Person : NSObject {
  2. int _no;
  3. }
  4. @interface Student : Person {
  5. int _age;
  6. }

NSObject、Person、Student的底层结构如下:

  1. struct NSObject_IMPL {
  2. Class isa;
  3. };
  4. struct Person_IMPL {
  5. struct NSObject_IMPL NSObject_IVARS;
  6. int _age;
  7. };
  8. struct Student_IMPL {
  9. struct NSObject_IMPL Person_IVARS;
  10. int _no;
  11. };

Person和Student的内存结构为:
image.png
分别打印person和stu对象的class_getInstanceSize和malloc_size

  1. NSLog(@"person = %zd",class_getInstanceSize([Person class]));
  2. NSLog(@"person = %zd",malloc_size((__bridge const void*)person));
  3. NSLog(@"stu = %zd",class_getInstanceSize([Student class]));
  4. NSLog(@"stu = %zd",malloc_size((__bridge const void*)stu));

打印结果:

  1. ~:person = 16
  2. ~:person = 16
  3. ~:stu = 16
  4. ~:stu = 16

虽然person成员变量所占实际大小是12个字节,但是class_getInstanceSize实际上返回的是对齐后的内存大小,所以应该是8的倍数,返回的就是16。

*内存对齐:结构体的最终大小必须是最大成员大小的倍数

2.4、OC的内存对齐

创建一个Student类继承于NSObject

  1. @interface Student : NSObject {
  2. int _no;
  3. int _age;
  4. int _height;
  5. }

分别获取class_getInstanceSize和malloc_size

  1. NSLog(@"stu = %zd, %zd",class_getInstanceSize([Student class]),malloc_size((__bridge const void*)stu));

打印结果:

  1. ~: 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个字节。