美文网首页
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