美文网首页iOS相关
OC对象的本质

OC对象的本质

作者: __weak | 来源:发表于2021-02-03 09:35 被阅读0次

    OC对象的本质

    • 我们平常编写的 Objective-C 代码,底层实现其实都是 C/C++ 代码
    • 具体的实现过程,就是 Objective-C ——>C/C++———>汇编语言———>机器语言
    image
    • 注意所以Objective-C的面向对象都是基于C/C++的数据结构实现的

    Objective-C转换成C/C++代码

    创建一个命令行项目

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

    在项目的目录下运行clang -rewrite-objc main.m -o main.cpp显示如下,生成.cpp文件

    image

    命令:clang -rewrite-objc main.m -o main.cpp

    • clang Xcode 内置的 LLVM 编译器

    • -rewrite-objc 从写objc代码

    • main.m 源文件

    • -o 输出

    • main.cpp cpp="c plus plus" 既是C++

      但是上面的命令会输出 多平台代码,内存大(9万多行代码),不便于读取,Windows ,MAC,因此一般需要我们指定平台,用于iPhone手机例如 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

      命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出文件

      与上条命令相比较多了几点:

      • xcrun Xcode run
      • -sdk
      • iphoneos 指定平台
      • -arch 架构 模拟器(i386)、32bit(armv7)、5s后都是64bit(arm64)

    如果需要链接其他框架,使用-framework参数,比如 -framework UIKit

    image

    生成的main-arm64.cpp(3万多行代码),虽然比上面那个9万多行少许多,但也看的一脸懵逼啊,全局搜索int main 函数

    int main(int argc, const char * argv[]) {
     /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
    
        NSObject *obj=((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_c0_7nm4_r7s4xd0mbs67ljb_b8m0000gn_T_main_1b47c1_mi_0);
     }
     return 0;
    }
    
    

    然后搜索 NSObject_IMPL 显示下面的结构体,也就是说Objective-C编译后NSObject对象会编译成下面这个结构体

    struct NSObject_IMPL {
        Class isa; // 64位中占 8个字节
    };
    既是, NSObject *obj = [[NSObject alloc] init];
    一个NSObject 对象在内存中就是上面那个结构体形式
    也就是上面说的,C/C++的结构体支撑了Objective-C
    
    在进入Class中可以看到
    typedef struct objc_class *Class;
    释义:Class是一个指向结构体的指针 指针在32位中占4个字节,在64位中占8个字节
    
    

    然后,返回main.m文件,进入NSObject中可以看到和上面的NSObject_IMPL的结构

    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
        Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }
    
    

    这就是我们编写的OC语言转换成C++后的代码

    image

    思考 一个OC对象在内存中是如何布局的?

    • NSObject的底层实现

      image

    面试题

    一个NSobject对象占用多少内存

    • 导入 #import <objc/runtime.h>通过class_getInstanceSize可以查看内存
    • 但是NSObject对象内部只使用了8个字节的空间(bit64环境下,可以通过class_getInstanceSize函数获得)
    • 导入 #import <malloc/malloc.h>通过malloc_size可以查看内存
    • 系统分配了16个字节给NSobject对象(通过malloc_size函数获得)
     // 获得NSObject实例对象的成员变量所占用的大小 >> 8
    NSLog(@"%zd", class_getInstanceSize([NSObject class])); 
    
    // 获得obj指针所指向内存的大小 >> 16
    NSLog(@"%zd", malloc_size((__bridge const void *)obj)); 
    
    

    分析 :从下图可以看出,内存分配了16个字节,实际使用的使用8个字节存放isa

    image
    通过查看alloc 底层也能发现,size小于16 会自动取值为16
    // Class's ivar size rounded up to a pointer-size boundary.
        uint32_t alignedInstanceSize() {
            return word_align(unalignedInstanceSize());
        }
    
        size_t instanceSize(size_t extraBytes) {
            size_t size = alignedInstanceSize() + extraBytes;
            // CF requires all objects be at least 16 bytes.
            if (size < 16) size = 16;
            return size;
        }
    
    
    • 方式二
      代码中打断点,打印obj 的内存,然后使用Xcode查看内存
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSObject *obj = [[NSObject alloc] init];
        return 0;
    }
    
    

    使用Xcode 的Debug来调试
    Debug——>Debug Workflow ——>View Memory

    image

    通过打印可以看出前8个字节有值,后8个直接无值

    image
    • 方式三
      使用LLDB指令
     // print / p 打印指针
    (lldb) print obj
    (NSObject *) $0 = 0x0000000100753df0
    (lldb) p obj
    (NSObject *) $1 = 0x0000000100753df0
    // po 打印对象
    (lldb) po obj
    <NSObject: 0x100753df0>
    // 读取内存 memory read = x 
    (lldb) memory read 0x100753df0
    0x100753df0: 41 d1 a4 99 ff ff 1d 00 00 00 00 00 00 00 00 00  A...............
    0x100753e00: d0 3e 75 00 01 00 00 00 10 41 75 00 01 00 00 00  .>u......Au.....
    
    

    x/3xw 0x100753df0

    x memory read = x
    3 数量
    x格式
    w字节数
    0x100753df0 内存地址

    格式
    x是16进制 f是浮点 d是10进制
    字节大小
    bbyte 1字节 hhalf word 2字节 wword 4字节 ggiant word 8字节
    修改内存中的值
    memory write 内存地址 数值
    memory write 0x100753df0 10

    利用一个简单的对象再次探索OC对象的本质

    创建一个对象继承自NSObject

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

    使用上面的命令生成.cpp文件,查看对应的关键源码

    struct NSObject_IMPL {
        Class isa;
    };
    
    struct Student_IMPL {
        struct NSObject_IMPL NSObject_IVARS;  // 占8个字节 先写父类的实现,然后写自己的属性
        int _age;
        int _no;
    };
    // 上面两个代码等价于
    struct Student_IMPL {
        Class isa; // 8个字节
        int _age; // 4个字节
        int _no; // 4个字节
    };
    
    

    猜想 上面的Student对象占用多少内存

    • 根据内存对齐,结构体的大小必须是最大成员大小的倍数

    上面代码转换流程如下

    image

    执行下面代码,得知,分配了16个字节空间,使用了16个字节空间

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            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));
    
            //强制stu转化为结构体
            struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
            NSLog(@"no is %d, age is %d", stuImpl->_no, stuImpl->_age);
        }
        return 0;
    }
    
    

    思考 一个Person对象,一个Student对象占用多少内存空间

    // Person
    @interface Person : NSObject
    {
        int _age;
    } 
    // 16 = isa+_age = 8+4  但是根据内存对齐法则:结构体的大小必须是最大成员大小的倍数
    @end
    // Student
    @interface Student : Person
    {
        int _no;
    } 
    // 16  = isa+_age +_no = 8+4+4 刚好占用16个字节
    @end
    
    
    image

    内存分布图

    image

    再次思考 一个Person对象占用了多少内存空间

    @interface Person : NSObject
    {
        int _age;
        int _height;
        int _no;
    }
    // 根据底层实现原理 如下:
    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS; // 8
        int _age; // 4
        int _height; // 4
        int _no; // 4
    };  
    // 计算结构体大小,内存对齐,应该为结构体中最大成员大小的倍数 为 24
    
    // 打印
    Person *p = [[Person alloc] init];
    
    NSLog(@"%zd", sizeof(struct Person_IMPL)); // 24
    // 因为系统alloc分配内存为16的倍数,为了内存的速度,所以为 32
    NSLog(@"%zd %zd",
                  class_getInstanceSize([Person class]), // 24
                  malloc_size((__bridge const void *)(p))); // 32
    @end
    
    

    根据苹果开源库,查看得知
    开源库 https://opensource.apple.com/tarballs/libmalloc/

    image

    2个容易混淆的函数

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

    扩展

    可以使用gnu来窥探,内存分配

    相关文章

      网友评论

        本文标题:OC对象的本质

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