美文网首页IOS开发
OC对象的本质

OC对象的本质

作者: 越天高 | 来源:发表于2020-10-18 20:56 被阅读0次

    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 <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对象的本质

        本文链接:https://www.haomeiwen.com/subject/ceyfmktx.html