美文网首页
iOS底层原理 -- 对象内存大小

iOS底层原理 -- 对象内存大小

作者: X_L_F | 来源:发表于2018-12-06 10:09 被阅读17次
    一个OC对象占用多大内存?

    解决这个问题,需要探究OC对象的本质。
    实际上Object-C最后都会转化成C/C++混编代码,因此利用clang编译器将Object-C转为对应的代码

    // main.m
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSObject *objc = [[NSObject alloc] init];
            
        }
        return 0;
    }
    

    目标:将上述代码转为C/C++代码
    具体步骤:
    1、打开终端,cd到main.m所在文件夹
    2、输入命令

    xcrun -skd iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
    /**
    *  xcrun                Xcode提供的工具
    *  -sdk iphoneos        指定真机环境
    *  clang                前端编译器命令
    *  -arch arm64          指定64位编译器
    *  -rewrite-objc        指定重写objc语言
    *  main.m               源文件
    *  -o                   输出
    *  main.cpp             目标文件
    */
    

    说明:
    1、此处不直接使用clang转化C/C++语言,是因为没有指定环境,转化出来的文件会比较大,内容比较多。
    2、目标文件直接转为.cpp,而不是.c,是因为Object-C实际上由C和C++混合在一起的。如果只转为C语言,会少了很多语法。

    在main.cpp中会找到如下结构

    // main.cpp
    struct NSObject_IMPL {    // OC对象的本质
        Class isa;
    };
    
    typedef struct objc_class *Class;    // 实际上是一个指针
    

    解析出来后OC对象实际上包含了一个isa指针。而在64位编译器,一个指针的大小是8个字节。

    为了证明这个猜想,引入runtime框架来进行证明

    // mian.m
    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            /**
             * class_getInstanceSize 表示获取一个类生成的实例对象大小
             * %zu 输出size_t,size_t是C/C++标准在stddef.h中定义表示对象大小
             */
            NSLog(@"%zu", class_getInstanceSize([NSObject class]));
        }
        return 0;
    }
    
    result: 8
    

    结果为8,即8个字节。验证了上述的猜想。

    但是实际上真的是8个字节吗?运行一下下方代码。

    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    #import <malloc/malloc.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSObject *objc = [[NSObject alloc] init];
            NSLog(@"使用大小:%zu", class_getInstanceSize([objc class]));
            NSLog(@"实际分配大小:%zu", malloc_size((__bridge void *)objc));
        }
        return 0;
    }
    
    result:
    使用大小:8
    实际分配大小:16
    

    实际上class_getInstanceSize函数得到的是对象真正使用的内存大小,而malloc_size函数得到的是系统给对象分配的内存大小。

    总结如下
    问:
    一个OC对象占用多大内存?
    答:
    实际上分配了16个字节的存储空间给NSObject对象,真正只用了一个指针变量占用的大小(64位下占8个字节,32位下占4个字节)

    【这样才比较严谨】

    衍生题

    问题:求Person和Student分别占据的内存大小

    @interface Person : NSObject {
        int _age;
    }
    @end
    @implementation Person
    @end
    
    @interface Student : Person {
        int _num;
    }
    @end
    @implementation Student
    @end
    

    解决这个问题,利用上述的转化过程,将下面的main.m转化得到一个新的main.cpp

    // main.m
    #import <Foundation/Foundation.h>
    @interface Person : NSObject {
        int _age;
    }
    @end
    @implementation Person
    @end
    
    @interface Student : Person {
        int _num;
    }
    @end
    @implementation Student
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
        }
        return 0;
    }
    

    提取main.cpp中的有用信息,得到如下结果

    struct NSObject_IMPL {
        Class isa;
    };
    
    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;   // 实际上替换为NSObject_IMPL,一个isa
        int _age;
    };
    
    struct Student_IMPL {
        struct Person_IMPL Person_IVARS;   // 实际上为替换为Person_IMPL,一个isa指针,一个int
        int _num;
    };
    

    前提:64位环境下,指针变量占8个字节,int变量占4个字节。

    1、Person由一个指针变量isa,和一个int变量_age组成。因此, 8+4=12?
    实际上为8+(4+4空白)=16个字节,由于内存对齐的原因,因此有4个空白字节进行了填充。

    2、Student由一个指针变量isa、一个int变量_age,一个int变量_num。此时原先填充的4个空白,被_num占据了。Student对象此时为8+4+4 = 16字节。

    为了验证猜想,利用上述的class_getInstanceSize进行验证。

    // maim.m
    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    
    @interface Person : NSObject {
        int _age;
    }
    @end
    @implementation Person
    @end
    
    @interface Student : Person {
        int _num;
    }
    @end
    
    @implementation Student
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            NSLog(@"%zu", class_getInstanceSize([Person class]));
            NSLog(@"%zu", class_getInstanceSize([Student class]));
        }
        return 0;
    }
    
    result: 16 16
    

    结果为16和16,即都为16个字节。验证了上述的猜想。

    作答:
    Person占16个字节,Student占16个字节(64位环境下)

    相关文章

      网友评论

          本文标题:iOS底层原理 -- 对象内存大小

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