iOS底层原理 - OC对象的本质

面试题引发的思考:

Q: 一个NSObject对象占用多少内存?

// TODO: -----------------  main  -----------------
#import 
#import 
#import 

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc] init];
        // 创建一个实例对象,实际上分配多少内存 - 16
        NSLog(@"%zd", malloc_size((__bridge const void *)obj));

        // 创建一个实例对象,至少需要多少内存 - 8
        NSLog(@"%zd", class_getInstanceSize([NSObject class]));
    }
    return 0;
}
  • 系统分配了16个字节给NSObject对象(通过malloc_size函数获得);
    由OC源码可知:分配给对象的内存为16个字节的倍数,最少为16个字节。

  • NSObject对象内部使用了8个字节的空间(64bit环境下,通过class_getInstanceSize函数获得)。

Q: OC的对象、类主要是基于C\C++的什么数据结构实现的呢?

  • 结构体。

1. OC底层实现

  • 我们平时编写的OC代码,底层实现其实都是C\C++代码;
  • OC的面向对象都是基于C\C++的数据结构实现的。
代码转换过程

将OC代码转换为C\C++代码,使用xcrun:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件

OC生成NSObject对象:

NSObject *obj = [[NSObject alloc] init];

在main-arm64.cpp中搜索NSObject_IMPL(IMPLimplementation实现):

NSObject_IMPL

由OC源码可知:

  • OC的对象是基于C的结构体实现的;此结构体只有一个成员:isa指针,而指针在64位架构中占8个字节;
  • 即:一个NSObject对象占用的内存是8个字节。

2. 自定义对象占用内存

实现一个自定义类:

// TODO: -----------------  Student类  -----------------
@interface Student: NSObject{
   @public
   int _no;
   int _age;
}
@end

@implementation Student
int main(int argc, const char * argv[]) {
   @autoreleasepool {
       Student *stu = [[Student alloc] init];
       stu -> _no = 4;
       stu -> _age = 5;
   }
   return 0;
}
@end

同上述步骤生成C++文件,在文件中找出Student_IMPL

Student_IMPL

结构体Student_IMPL中第一个参数是结构体NSObject_IMPL,而NSObject_IMPL内部是Class isa
struct NSObject_IMPL NSObject_IVARS等同于Class isa,那么可转化为下图代码:

转化后的Student_IMPL

此结构体占用的存储空间为:isa指针(指针8个字节) + _no(int4个字节) + _age(int4个字节) 共16个字节空间;
Sutdent对象占用16个字节的存储空间

假设stu的地址为0x100400110,Sutdent对象的变量则分别对应以下的地址:

Student对象的存储空间

验证一下:

// TODO: -----------------  Student类  -----------------
@interface Student: NSObject{
   @public
   int _no;
   int _age;
}
@end

@implementation Student
int main(int argc, const char * argv[]) {
   @autoreleasepool {
       Student *stu = [[Student alloc] init];
       // 赋值
       stu -> _no = 4;
       stu -> _age = 5;
       
       // 强制转化
       struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
       NSLog(@"_no = %d, _age = %d", stuImpl->_no, stuImpl->_age); 
   }
   return 0;
}
@end

// 打印结果
Demo[1234:567890] _no = 4, _age = 5

先给Student对象赋值_no = 4, _age = 5,那么它的内存中也存储_no = 4, _age = 5,然后将OC对象强制转换为Student_IMPL结构体,通过结构体的指针访问_no_age,输出结果也是_no = 4, _age = 5,说明stu这个指针它指向的对象,是结构体,证明了我们的猜想。


3. 延伸问题:

Q: 以下一个Person对象,一个Student对象占用多少内存空间,即打印结果是什么?

// TODO: -----------------  Person类  -----------------
@interface Person : NSObject {
    int _age;
}
@end

@implementation Person
@end

// TODO: -----------------  Student类  -----------------
@interface Student : Person {
    int _no;
}
@end

@implementation Student
@end

// TODO: -----------------  main  -----------------
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"Person - %zd  Student - %zd",
              class_getInstanceSize([Person class]),
              class_getInstanceSize([Student class])
              );
    }
    return 0;
}

// 打印结果
Demo[1234:567890] Person - 16  Student - 16

根据上文可以得出下面的关系图:

Person对象和Student对象内存结构

Q: 奇怪了,不应该打印Person - 12 Student - 16吗?

Person对象确实只使用12个字节,但是由于内存对齐(结构体的大小必须是最大成员大小的倍数)的原因,所以Person对象占用了16个字节;即isa指针(指针8个字节)的倍数。

由OC源码可知:

分配给对象的内存为16个字节的倍数,最少为16个字节。


4. 更多:

Q: 以下代码打印结果是什么?

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

@implementation Person
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        NSLog(@"%zd %zd",
              class_getInstanceSize([Person class]),
              malloc_size((__bridge const void *)(p)));
    }
    return 0;
}

// 打印结果
Demo[1234:567890] 24 32

你可能感兴趣的:(iOS底层原理 - OC对象的本质)