美文网首页
iOS-浅谈OC对象的本质

iOS-浅谈OC对象的本质

作者: 晴天ccc | 来源:发表于2019-04-16 17:49 被阅读0次

    目录

    • Objective-C的本质
    • NSObject的底层实现
    • 一个NSObject对象的底层内存分布情况
    • 内存占用查看工具
      ----Xcode方式查看
      ----控制台LLDB命令方式查看
    • 拓展Student对象内存布局
    • 内存对齐-继承结构
    • OC内存对齐
    • 其他补充
      ----数据类型内存占用表
      ----容易混淆的2个函数
      ----Xcode实时查看内存数据
      ----常用LLDB指令

    Objective-C的本质

    我们平时编写的Objective-C代码底层是由C\C++实现的。OC的面向对象是基于C\C++的数据结构实现的。OC的对象、类主要基于C\C++的结构体实现的。OC代码转换过程:

    NSObject的底层实现

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

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

    // OC代码
    @interface NSObject {
        Class isa;
    }
    
    // 底层实现
    struct NSObject_IMPL {
        Class isa;
    };
    

    在C++中,NSObject是一个结构体,内部只有一个isa指针变量
    isa指针的大小就是结构体所占内存大小,结构体的内存地址就是isa指针的地址。
    在32位系统下占用4个字节,64位系统占用8个字节

    一个NSObject对象的底层内存分布情况

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

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

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

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

    inline size_t instanceSize(size_t extraBytes) const {
        if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
            return cache.fastInstanceSize(extraBytes);
        }
    
        size_t size = alignedInstanceSize() + extraBytes;
        // CF requires all objects be at least 16 bytes.  
        if (size < 16) size = 16;
        return size;
    }
    

    NSObject对象使用了前8个字节,后8个字节是空的,NSObject对象数据结构如下:

    内存查看工具

    • Xcode

    利用Xcode可以查看对象的内存布局,Debug -> Debug Workfllow -> View Memory,输入NSObject对象地址:

    • 控制台LLDB`命令输出

    也可以在控制台通过LLDB命令进行查看,复制obj对象的内存地址,然后在控制台输入memory read 粘贴内存地址

    Printing description of obj:
    <NSObject: 0x28121c000>
    (lldb) memory read 0x28121c000
    0x28121c000: 49 ef 4d f0 01 00 00 01 00 00 00 00 00 00 00 00  I.M.............
    0x28121c010: 49 ef 4d f0 01 00 00 01 00 00 00 00 00 00 00 00  I.M.............
    (lldb) 
    

    拓展Student对象内存布局

    创建一个继承于NSObject的类Student

    @interface Student : NSObject {
        @public
        int _no;
        int _age;
    }
    

    通过观察可以发现Student继承自NSObject,而NSObject底层的结构体只有一个isa,所以Student的底层实现为:

    struct NSObject_IMPL {
        Class isa;
    };
    
    struct Student_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        int _no;
        int _age;
    };
    

    我们对进行Student类进行初始化并赋值

        Student *stu = [[Student alloc] init];
        stu->_no = 4;
        stu->_age = 5;
        NSLog(@"%zd", class_getInstanceSize([Student class]));
        NSLog(@"%zd", malloc_size((__bridge const void *)stu));
    
    16
    16
    

    Student实例对象包含父类的isa指针和自己的成员变量,isa占八个字节,int声明的对象占四个字节,所以Student在开辟的内存空间大小就是8+4+4=16
    我们看一下内存地址:

    Printing description of stu:
    <Student: 0x101012770>
    (lldb) memory read 0x101012770
    0x101012770: 61 81 00 00 01 00 00 01 04 00 00 00 05 00 00 00  a...............
    0x101012780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
    (lldb) 
    

    内存地址是按照第一个对象的地址来的。所以可以推算出Student在堆中内存地址分布情况

    内存对齐-继承结构

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

    @interface Person : NSObject {
        int _no;
    }
    @interface Student : Person {
        int _age;
    }
    

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

    struct NSObject_IMPL {
        Class isa;
    };
    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        int _age;
    };
    
    struct Student_IMPL {
        struct NSObject_IMPL Person_IVARS;
        int _no;
    };
    

    PersonStudent的内存结构为:

    分别打印personstu对象的class_getInstanceSizemalloc_size

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

    打印结果:

    person = 16
    person = 16
    
    stu = 16
    stu = 16
    

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

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

    OC的内存对齐

    创建一个Student类继承于NSObject

    @interface Student : NSObject {
        int _no;
        int _age;
        int _height;
    }
    NSLog(@"stu = %zd",class_getInstanceSize([Student class]));
    NSLog(@"stu = %zd",malloc_size((__bridge const void*)stu));
    

    打印结果:

    24
    32
    

    分析:成员变量所占内存大小8 + 4 + 4 + 4 = 20,根据内存对其规则,应该是8的倍数,所以结果是24,那么为什么malloc_size读取的是32

    class_getInstanceSize是获取创建这个实例对象:【至少需要多少内存】。
    malloc_size是获取创建这个实例对象:【实际上分配了多少内存】。
    OC内存对齐单位是16或者16的倍数
    所以创建Student对象需要24个字节,但是实际上分配了32个字节

    其他补充

    • 数据类型内存占用表
    • 容易混淆的2个函数
    创建一个实例对象,至少需要多少内存?
    #import <objc/runtime.h>
    class_getInstanceSize([NSObject class]);
    
    创建一个实例对象,实际上分配了多少内存?
    #import <malloc/malloc.h>
    malloc_size((__bridge const void *)obj);
    
    • 实时查看内存数据
    Debug -> Debug Workfllow -> View Memory  
    
    • 常用LLDB指令
    print、p:打印
    po:打印对象
    // 读取内存
    memory read/数量格式字节数  内存地址
    x/数量格式字节数  内存地址
    x/3xw  0x10010
    
    

    相关文章

      网友评论

          本文标题:iOS-浅谈OC对象的本质

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