写在前面
苹果官方源码下载:opensource.apple.com/tarballs -> 搜索objc/4 -> 下载最新源码
Objective-C的本质
在iOS开发中如果我们使用OC编写的代码,其底层都是C/C++代码。
所以我们可以理解为:Objective-C中面向对象都是基于C/C++的数据结构【结构体】实现的。
在面试的时候有的面试官会问到我们OC对象的本质是什么?我们可以说是【结构体】。
生成C++文件的一些命令:
clang -rewrite-objc main.m -o main.cpp
:无法区分平台 不建议使用
xcrun -sdk iphonesimulator clang -rewrite-objc main.m -o main.cpp
:模拟器
xcrun -sdk iphoneos clang -rewrite-objc main.m -o main.cpp
:真机
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 源文件名 -o 输出的cpp文件名
:arm64架构 这个命令我用得最多
OC对象的本质
NSObject对象的底层实现
先从一个最简单的例子说起:
在main函数里面创建一个NSObject
的对象:NSObject *objc = [[NSObject alloc] init];
使用上面的命令生成相应的C++代码如下:
分析C++文件:
// NSObject Implementation(NSObject 底层实现)
struct NSObject_IMPL {
Class isa;
};
// 结构体
typedef struct objc_class *Class;
NSObject *objc = [[NSObject alloc] init];
这句代码的意思:创建完对象之后,给这个对象分配完存储空间,把这个对象的内存地址赋值给objc这个指针 这样我们才能通过objc这个指针找到这个对象。
如何让项目中的某个文件不参与编译?
项目设置 - Build Phases - Compile Sources - 删除不需要参与编译的文件
class_getInstanceSize 和 malloc_size
创建一个实例对象 至少需要多少内存?
#import <objc/runtime.h>
NSObject *objc = [[NSObject alloc] init];
// 输出值:8
NSLog(@"class_getInstanceSize : %zu",class_getInstanceSize(objc.class));
// 源码
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
创建一个实例对象 实际分配了多少内存?
#import <malloc/malloc.h>
// 输出值:16
malloc_size((__bridge const void *)obj);
源码追踪:
按照上面的源码可以看到:存在【内存对齐】的概念 当size<16时 会赋值为16。
窥探NSObject的内存
拿到NSObject
对象的内存地址:0x6000021ac070
通过Debug - Debug Workflow - View Memory(shift + command + M) 查看内存情况:
内存分析:
上图是16进制的内存分布。
一个16进制位对应4个二进制位 比如二进制的1001 等于 16进制的9
上图中的【10】对应的就是8个二进制位 也就是一个字节
从80开始就是新的对象的内存 这样我们可以看到NSObject确实是分配了16个字节。
1、memory read(x):读取内存
2、memory write:改写内存
更复杂的对象的底层探究
🌰 现创建一个Person对象继承自NSObject,代码如下:
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@end
@implementation Person
@end
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
Person *person = [[Person alloc] init];
person.name = @"Sunny";
person.age = 12;
// 输出值:24
NSLog(@"class_getInstanceSize: %zu",class_getInstanceSize(person.class));
// 输出值:32
NSLog(@"malloc_size: %zu",malloc_size((__bridge const void *)(person)));
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
转化为C++底层代码如下:
struct NSObject_IMPL {
Class isa;
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
int _age;
};
查看person
对象的内存分布如下:
分析:
f8 d5 e1 07 01 00 00 00
段存储的是person
对象的内存地址。0c 00 00 00 00 00 00 00
段存储的是age的值12。a0 80 e1 07 01 00 00 00
段存储的是name的值Sunny。计算机里面分大端模式、小端模式,不同的端读取数据的方式不一样,在iOS里面 是按照【小端模式】的方式读取数据,【小端模式】即是从地址较大的位置开始读取 比如
0c 00 00 00 00 00 00 00
段读取就是:0x0000000c = 12。注意:结构体的大小必须是最大成员大小的【倍数】,比如上面的例子中class_getInstanceSize的值为24而不是20。
class_getInstanceSize:至少需要的内存大小 值大于等于16 并且是8的倍数
malloc_size:实际分配的内存大小 必须是16的倍数
一些基本数据类型占用的字节数:
BOOL:1位
int:4位
float:4位
double:8位
NSInteger:8位
NSUIteger:8位
CGFloat:8位
sizeof
可以得出某种数据类型所占的字节数:
size_t BOOL_ByteCount = sizeof(BOOL);
size_t NSInteger_ByteCount = sizeof(NSInteger);
其他总结
1、一个实例对象的内存里面为什么不存储方法二是只存储了成员变量?
方法是相同的 只需要存储一份即可,存储在【类对象】的方法列表里面,成员变量可以有不同的值所以需要存储在实例对象的内存里面。
2、sizeof
是在编译阶段就需要确认的 是个运算符。
3、内存对齐是为了提高CPU的访问速度。
更多技术文章欢迎移步Sunny的个人技术博客。