OC 底层 对象的本质

1、OC 对象的本质

探寻OC对象的本质,咱们平时编写的Objective-C代码,底层实现其实都是C\C++代码。bash

OC的对象都是经过基础C\C++的结构体实现的。架构

OC 转换为 C++

咱们经过建立OC对象,并将OC文件转化为C++文件来探寻OC对象的本质,以下代码:框架

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

复制代码

使用 clang 将 OC 代码转换为 C++ 代码iphone

// 这种方式没有指定架构例如arm64架构 其中cpp表明(c plus plus)生成 main.cpp
clang -rewrite-objc main.m -o main.cpp 
复制代码

使用 Xcode 工具 xcrun 进行转换函数

// 能够指定 arm64 架构 若是须要连接其余框架,使用-framework参数。好比-framework UIKit
// -o 输出的 cpp 文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 
复制代码

NSObject 对象的内存布局

NSObject 对象的 OC 定义以下:(咱们能够在Xcode中点击进去查看)工具

@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}


复制代码

NSObject 对应的结构体是 NSObject_IMPL结构体,NSObject_IMPL 定义以下:布局

struct NSObject_IMPL {
    Class isa;
};
typedef struct objc_class *Class;

复制代码

NSObject_IMPL结构体只有一个成员 isa 指针,而指针在64位架构中占8个字节。也就是说一个NSObjec对象所占用的内存是8个字节。ui

NSObject *objc = [[NSObject alloc] init];
复制代码

上述一段代码中系统为 NSObject 对象分配 8个字节的内存空间,用来存放一个成员 isa 指针。那么 isa 指针这个变量的地址就是结构体的地址,也就是 NSObjcet 对象的地址。spa

NSObject 只须要8字节的空间,但实际上,NSObject 对象占用 16 字节空间,iOS 下对象至少占用 16 字节指针

NSObject 子类对象的内存布局

代码以下:

@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;
        
        NSLog(@"%@",stu);
    }
    return 0;
}
@end

复制代码

对应的 C++ 结构体 Student_IMPL 以下:

struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
    int _age;
};
复制代码

所以此结构体占用多少存储空间,对象就占用多少存储空间。所以结构体占用的存储空间为,isa指针8个字节空间,int类型 _no 占用4个字节空间,int类型 _age 占用4个字节空间,共16个字节空间。

image.png

sutdent对象的3个变量分别有本身的地址。而stu指向isa指针的地址。所以stu的地址为0x100400110,stu对象在内存中占用16个字节的空间。而且通过赋值,_no里面存储4 ,_age里面存储5。

验证对象的内存布局

struct Student_IMPL {
    Class isa;
    int _no;
    int _age;
};
@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); // 打印出 _no = 4, _age = 5
    }
    return 0;
}
@end
复制代码

上述代码将oc对象强转成Student_IMPL的结构体。也就是说把一个指向oc对象的指针,指向这种结构体。最终能够转化成功,因此对象在内存中的布局与结构体在内存中的布局相同。由此说明stu这个对象指向的内存确实是一个结构体。

更复杂的继承关系

///  Person 
@interface Person : NSObject
{
    int _age;
}
@end
/// Student
@interface Student : Person
{
    int _no;
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"%zd %zd",
              class_getInstanceSize([Person class]),
              class_getInstanceSize([Student class])
              );
    }
    return 0;
}
复制代码

咱们发现只要是继承自NSObject的对象,那么底层结构体内必定有一个isa指针。那么他们所占的内存空间是多少呢?单纯的将指针和成员变量所占的内存相加便可吗?上述代码实际打印的内容是16 16,也就是说,person对象和student对象所占用的内存空间都为16个字节。 其实实际上person对象确实只使用了12个字节。可是由于内存对齐的缘由。使person对象也占用16个字节。

编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,而后寻找内存地址能是该基本数据类型的整倍的位置,做为结构体的首地址。将这个最宽的基本数据类型的大小做为对齐模数。
为结构体的一个成员开辟空间以前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是不是本成员的整数倍,如果,则存放本成员,反之,则在本成员和上一个成员之间填充必定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
复制代码

咱们能够总结内存对齐为两个原则:

原则 1. 前面的地址必须是后面的地址正数倍,不是就补齐。
原则 2. 整个Struct的地址必须是最大字节的整数倍。
原则 3. OC 对象至少占 16 字节
复制代码

经过上述内存对齐的原则咱们来看,person对象的第一个地址要存放isa指针须要8个字节,第二个地址要存放_age成员变量须要4个字节,根据原则一,8是4的整数倍,符合原则一,不须要补齐。而后检查原则2,目前person对象共占据12个字节的内存,不是最大字节数8个字节的整数倍,因此须要补齐4个字节,所以person对象就占用16个字节空间。

而对于student对象,咱们知道sutdent对象中,包含person对象的结构体实现,和一个int类型的_no成员变量,一样isa指针8个字节,_age成员变量4个字节,_no成员变量4个字节,恰好知足原则1和原则2,因此student对象占据的内存空间也是16个字节。

代码方式获取对象占用空间大小

建立一个实例对象,至少须要多少内存?
#import <objc/runtime.h> class_getInstanceSize([NSObject class]);
●
●
●
建立一个实例对象,实际上分配了多少内存?
#import <malloc/malloc.h> malloc_size((__bridge const void *)obj);

备注:sizeof()不是一个函数

复制代码

因为一些系统内存分配机制,内存对齐等,实际分配的内存可能要比实际须要的内存大一些。

相关文章
相关标签/搜索