美文网首页
OC底层原理(一):一个NSObject对象占多少内存?

OC底层原理(一):一个NSObject对象占多少内存?

作者: TheEnded | 来源:发表于2019-05-28 11:25 被阅读0次

    对OC底层原理探究的开篇,也是对mj课程的回顾总结,尽量能都记录下来吧。
    这里其实也是一道面试题,那么我们就看看这个答案是什么。

    问:一个NSObject对象占多少内存?
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSObject *obj = [[NSObject alloc] init];
        }
        return 0;
    }
    

    我们都知道OC编译之后会成为C/C++代码,那么编译之后到底是什么样子呢?XCode的默认编译器是clang,我能可以通过clang命令直接编译并运行一段OC代码。

    打开terminal,进入mian.m文件所在的路径,
    输入以下指令把OC代码转成C++(编译之后是C/C++,使用cpp后缀对C来说也正常)

    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
    

    可以看到当前文件夹下多了一个main-arm64.cpp文件(图中main.cpp文件是未指定架构变异出来的文件)


    image.png

    为了方便查看我们把这个文件拖入工程中。
    在文件中搜索NSObject_IMPL,可以找到NSObject的编译之后的样子


    image.png
    其实我们也知道直接按住command进入NSObject也可以看到,它是这个样子
    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
        Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }
    

    对于Class我们按住command再进去,它是这个样子

    typedef struct objc_class *Class;
    

    其实就是NSObject结构体中有一个Class结构体类型的指针,注意是指针,指针在64位系统中占8个字节。
    --------分割线--------
    那么它到底是不是占8个字节呢,我们可以log一下。

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSObject *obj = [[NSObject alloc] init];
            NSLog(@"%zd", class_getInstanceSize([NSObject class]));
            NSLog(@"%zd", malloc_size((__bridge const void *)obj));
        }
        return 0;
    }
    

    最终结果


    image.png

    我们知道class_getInstanceSize方法是获取实例变量的大小,malloc_size是分配内存大小。对于class_getInstanceSize方法获取的大小应该是我们能想到的,但是malloc_size为什么会返回16呢?我们继续看源码,我们首先看class_getInstanceSize,这个方法在runtime源码中可以查到,我这里直接贴出来

    //两个方法在不同的地方这里一起放过来了
     size_t class_getInstanceSize(Class cls)
    {
        if (!cls) return 0;
        return cls->alignedInstanceSize();
    }
    // Class's ivar size rounded up to a pointer-size boundary.
    //我们看到这里注释就是说返回ivar的大小(注意这里的一些修饰词rounded up 、 boundary 以及方法word_align)
    uint32_t alignedInstanceSize() {
        return word_align(unalignedInstanceSize());
    }
    

    所以这里返回8就是正确的了。我们再看malloc_size,这个是指的分配内存,分配内存我们知道是调用allocWithZone:的时候进行分配,我们再找下这个的具体实现。


    image.png
    进入这个方法,最后我们找到_class_createInstanceFromZone这个方法 image.png
    在看instanceSize的实现,我们就能知道这里为什么是分配了16个字节大小的空间。
    image.png

    到这里就结束了吗?继续看。
    如果我们有一个Person类,Person类里有一个成员变量int _age,分析下下面的代码

     @interface Person : NSObject
    {
        int _age;
    }
    @end
    
    @implementation Person
    
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
           Person *person = [[Person alloc]init];
           NSLog(@"person - %zd", class_getInstanceSize([Person class]));
           NSLog(@"person - %zd", malloc_size((__bridge const void *)person));
        }
        return 0;
    }
    

    这里会输出什么?我们知道int占据4个字节大小,根据我们上面所看到的,Person最终转换为这个样子

    struct NSObject_IMPL {
        Class isa;
    };
    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS; // 8
        int _age; // 4
    }; 
    

    所以我们猜应该是12 ,16?看结果吧


    image.png

    竟然都是16 ,为什么呢?
    这就就是继续看上面那个alignedInstanceSize方法,我让大家注意这里的关键词,其实这里是有一个计算机内存对齐的原因,其中有1条是,结构体的大小是结构体内部成员大小最大的那个的整数倍(这里是不准确的说法,其中还涉及到操作系统的#pragram pack()指定系数,具体就百度吧)。这里内部成员大小最大的就是isa指针了,长度是8,所以要是8的整数倍,12的基础上补上4位也就是16。

    你以为到这里就完了?我们再继续,我们在person中再添加一个int _no; int _height;
    这次的输出结果是什么呢?
    根据上面讲的,Person编译后应该是这样

    struct NSObject_IMPL {
        Class isa;
    };
    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS; // 8
        int _age; // 4
        int _no; //4
        int _height; //4
    }; 
    

    关于内存对齐我们也了解,所以推断出这里log应该是24(8+4+4+4+补齐4), 24?
    实际结果

    image.png
    what F***?为什么实力对象占用24字节,分配内存却给了32呢?我们明明是只看到了不足16返回16呀。
    这里其实又涉及到了内存分配的对齐,大家注意和结构体的内存对齐是不一样的。这里内存分配其实是按照一个bucket,iOS堆空间的内存分配时,这里是16。也就是分配的内存是16的倍数。
    具体可以看源码。打开网址https://opensource.apple.com/tarballs/,搜索libmalloc,选择编号最大的一份下载(编号最大=最新)。
    PS:这个分配内存的源码太复杂了,其实我也没有找到在哪写的对齐是16...,有了解的同学可以给指点一下。
    面试题解答

    问:一个NSObject对象占用多少内存?
    答:NSObject对象只占用8个字节(64bit下)
    系统分配了16个字节给NSObject对象(通过malloc_size函数获得)

    好了,第一节的内容就结束了,有不对的地方可以指出,欢迎大家讨论。

    相关文章

      网友评论

          本文标题:OC底层原理(一):一个NSObject对象占多少内存?

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