OC对象的本质

OC和C_C++OC和C_C++

  • 一个NSObject对象占多少内存
        NSObject *person = [[NSObject alloc] init];

也就是说person指针指向的这段内存空间,占有多少内存空间?
要想知道他在内存中占有多少空间,就要知道他在内存是怎么布局的,在内存中包含哪些内容,搞清楚这段代码的本质是什么,
我们平时编写的OC代码,底层实现其实都是C\C++代码,

截屏2020-10-18 下午6.45.28.png

编译器会将C\C++转成汇编,然后再转成机器语言运行
所以Objective-C的面向对象都是基于C\C++的数据结构实现的
思考:Objective-C的对象、类主要是基于C\C++的什么数据结构实现的?
结构体

将Objective-C代码转换为C\C++代码

可以安装一个gotocell可以快速定位到终端
代码之后之间的转换肯定是编译器编译的结果,所以要用编译器相关的工具,这里使用使用的clang,clang是xcode内置的编译器llvm的编译器前端
clang -rewrite-objc main.m -o main.cpp
因为我们生成文件是c和c++都有的所有最好生成CPP文件(c plus plus),
不建议直接用上面的来直接转,因为编译也要看我们要转成什么平台的代码,不同平台支持的代码肯定是不一样的,因为我们的代码会转成汇编,会变得运行要根据硬件不同来运行,所以我们只希望生成IOS平台来生成,
模拟器(i386) 32bit(armv7),64bit(arm64)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
没有指定架构的生成文件的大小3.5M,指定之后只有1M多,

截屏2020-10-18 下午7.11.04.png

NSObject对象的内存本质

我们上面生成文件,就是想看看 [[NSObject alloc] init];他的本质是什么,
我们再相关的文件中可以找到NSObject_IMPL

struct NSObject_IMPL {
    Class isa;
};

如果将我们cpp拖进xcode项目,编译会报错,因为cpp是临时生成的,还有一个就是cpp文件也有一个main函数,一个程序只能有一个main函数,所有也会出错,
可以再Xcode的编译文件中,将cpp去掉,


截屏2020-10-18 下午7.18.34.png

NSObject_IMPL他的意思就是NSObject Implementation,也就是一个NSObject的底层实现,
如果我们直接通过Xcode点进去看一下NSObject的实现可以发现

@interface NSObject  {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
@end
简化成
@interface NSObject {
    Class isa  ;
}
@end

OC的类他的底层实现就是结构体,C\C++的结构体支撑的OC的面向对象

  • 问题一个OC对象内存中是如何布局的?
    NSObject的底层实现,头文件里面


    截屏2020-10-18 下午7.26.57.png

    底层实现


    截屏2020-10-18 下午7.34.12.png

    isa 我们可以按进去看一下,他的类型是这个样子,是一个指向结构体的指针
typedef struct objc_class *Class;

既然isa是一个指针那么他在64bit的机器占据8个字节,32bit4个字节,
所以alloc相当于给右边的结构体分配存储空间,分配完成之后,会又一个指针,指向我们的分配的这段空间,假设我们分配的空间isa的地址0x100400110


截屏2020-10-18 下午7.40.03.png

class_getInstanceSize、malloc_size.

一个NSObject对象占多少内存?
根据我们上面代码分析,我们可能会觉得一个NSObject占用8个字节的内存,实际上不是,实际上是16个字节,但是他利用起来的只有8个字节的大小,我们可以用runtime来验证一下,
class_getInstanceSize用来获取一个类的实力对象的大小,实例就是我们通过alloc出来的具体对象,
malloc_size获得指针所指向的内存大小

       //获得NSObject的实例对象的成员变量所占用的大小
       NSLog(@"%zu大小", class_getInstanceSize([NSObject class]));//8字节
       //获得指针所指向的内存大小
       NSLog(@"%zd", malloc_size(CFBridgingRetain(person)));// 16字节

苹果开源网站 opensource.apple.com->objc(https://opensource.apple.com/tarballs/objc4/)找到最新的进行下载
解压之后我们直接打开项目,搜索class_getInstanceSize查看源码发现,他会调用一个

截屏2020-10-18 下午8.06.33.png

返回就是实例对象的成员变量的大小
实际内存中
截屏2020-10-18 下午8.10.15.png

面试题回答

我们还是回到IOS的源码
通过alloc查看他是否真的是占用了16个字节,实际上调用了allocWithZone



截屏2020-10-18 下午8.14.56.png

在它里面调用了calloc函数,这里需要传一个size参数,


size

所以一旦发现小于16他就会分配16个字节,因为他规定所有的对象最低是16个字节,这是corefoundation硬性规定的

  • 一个NSObject对象占多少内存?

系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)

窥探NSObject的内存

我们可以从另一个角度去验证他是否占了16个字节的内存,Xcode工具,打断点,查看对象的地址


截屏2020-10-18 下午8.32.19.png

然后通过debug->debugworkflow->view memory


截屏2020-10-18 下午8.33.49.png

因为内存使用的是16进制,所以两位占一个字节,
通过内存图我们可以看出来我们的对象在内存的分布形式,有颜色的部分就是我们的NSObject的内存分布,可以看出来,他只是用了前8个字节

内存分布16.png

我们也可以通过打断点之后使用(lldb)调试器来进行调试

lldb常用的指令
print、p:打印

po:打印对象
内存读取
memory read/数量格式字节数 内存地址
x/数量格式字节数 内存地址
x/3xw 0x10010

  • 格式
    x是16进制,f是浮点,d是10进制

     字节大小
     b:byte 1字节,h:half word 2字节
     w:word 4字节,g:giant word 8字节
    
  • 修改内存中的值
    memory write 内存地址 数值
    memory write 0x0000010 10

使用例子


截屏2020-10-18 下午8.55.29.png

Student的本质

@interface Student : NSObject{
    @public
    int _no; //4字节
    int _age;//4字节
    
}
@end

@implementation Student
@end

通过终端生成C++代码

struct NSObject_IMPL
{
    Class isa; //8字节
};
struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
    int _age;
};

我们写的创建一个对象的方法

        Student *student = [[Student alloc] init];
        NSLog(@"%zd", class_getInstanceSize([student class]));//16
        NSLog(@"%zd", malloc_size((__bridge const void *)(student)));//16
        student->_no = 12;
        student->_age = 15;

在内存中的分布图其实是这个样子的


student对象的内存结构

因为结构体的地址就是第一个成员变量的地址,所以Student的地址就是isa的地址,内存分布是连续的,接下来的4个字节存储的_no的值,再4个是age的值,
我们可以强制将student指针,转成结构体类型


        struct Student_IMPL *stu = (__bridge struct Student_IMPL *)student;
        NSLog(@"no=%d,age=%d", stu->_no,stu->_age);//no=12,age=15

也可以正常的访问,进一步说明了,他本质上就是这个结构体类型

Student的内存结构

CPU从内存读取数据的方式分为大端和小端模式,IOS就是小端模式,读数据会从小的地址开始,所以他的四个字节是 04 00 00 00,

更复杂的集成模式

//Person
@interface Person:NSObject
{
    @public
    int _no;
}
@end

@implementation Person

@end
//Student
@interface Student:Person
{
    @public
    int _age;
}
@end
  • 思考:一个Person对象、一个Student对象占用多少内存空间?


    截屏2020-10-19 上午11.12.49.png

    答案 都是16个字节

内存对其,结构的内存大小,必须是最大成员变量的倍数,

因为Person占居了16个字节,但是最后面的4个字节是空的所以当Student继承了之后,会把自己age放到最后面的4个空字节上面,因此,student还是占据了16个字节,

如果我们增加一个int类型的成员变量,就会占32,

@interface Student:Person //16
{
    @public
    int _age;//4
    int _weight;//4
}
@end

我们通过alloc init出来的实例对象,是不会存储方法的,他只存有成员变量的值,因为方法在内存中指存在一份就够了,不需要每个实例都存一份

回答疑问

计算内存地址,就是一个个的往后数,

12-内存分布注意点

@interface Dog : NSObject
{
    //struct NSObject_IMPL NSObject_IVARS;//8个字节
    int _no;//4
    int _age;//4
    int _height;//4
} //理论上他在内存的大小是24字节
NSLog(@"%zd", class_getInstanceSize([dog class]));//24
NSLog(@"%zd", malloc_size((__bridge const void *)(dog)));//32

因为malloc_size结果是32 ,这个是他在内存中被分配的大小,因为他内存对其的单位是16,所以malloc的空间必须是16的倍数.

13-alloc的size分析

在calloc函数中分配内存的时候,传入的size确实就是实力对象的内存大小,24,但是底层内部的实现,会将这个歌内存惊醒对其计算,返回一个对其之后的size在进行实际分配,对其之后就是32

你可能感兴趣的:(OC对象的本质)