OC底层探索 - 对象本质 & 属性的内存存储顺序

72 阅读3分钟

对象的本质

新建一个 macOS App项目,在 main.m 中定义一个类

#import <Cocoa/Cocoa.h>

@interface People : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;

- (void)eat;

@end

@implementation People

- (void)eat {

}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
    }
    return NSApplicationMain(argc, argv);
}

打开终端,进入到 main.m 所在文件夹下,执行 clang 指令

clang -rewrite-objc main.m

会生成一个 main.cpp 文件。在 main.cpp 中搜索我们的类 People , 可以看到

#ifndef _REWRITER_typedef_People
#define _REWRITER_typedef_People
typedef struct objc_object People;
typedef struct {} _objc_exc_People;
#endif

extern "C" unsigned long OBJC_IVAR_$_People$_name;
extern "C" unsigned long OBJC_IVAR_$_People$_age;
struct People_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    NSString *_name;
};

// @property (nonatomic, copy) NSString *name;
// @property (nonatomic, assign) int age;
// - (void)eat;
/* @end */

// @implementation People

static void _I_People_eat(People * self, SEL _cmd) {
}

static NSString * _I_People_name(People * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_People$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_People_setName_(People * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct People, _name), (id)name, 0, 1); }

static int _I_People_age(People * self, SEL _cmd) { return (*(int *)((char *)self + OBJC_IVAR_$_People$_age)); }
static void _I_People_setAge_(People * self, SEL _cmd, int age) { (*(int *)((char *)self + OBJC_IVAR_$_People$_age)) = age; }
// @end

从中可以看出,我们的对象都是一个 objc_object 类型的结构体。People_IMPL 中看起来就是结构体的内容。不过 NSObject_IMPL 这个结构体不是我们定义的,搜索一下,发现

struct NSObject_IMPL {
    Class isa;
};

原来,这个结构体里面存放的就是 isa

所以OC的对象就是一个objc_object 类型的结构体,其中存放的是 isa 和 成员变量的值。

因为 isa 是固定大小,必然存在的。所以会对对象内存大小产生影响的就是成员变量的存储了。

属性在内存中的存储顺序

定义一个类,然后给类赋值

@interface TestClass : NSObject

// isa 8字节
@property (nonatomic, strong) NSString *name; // 指针 8字节
@property (nonatomic, assign) int age; // 4字节
@property (nonatomic, assign) double height; // 8字节

// 8+8+4+8=28 ,进行16字节对齐 = 32字节
@end


TestClass *test = [TestClass new];
test.height = 160.5;
test.age = 18;
test.name = @"hhhh";
NSLog(@"TestClass实例分配内存大小: %lu", malloc_size((__bridge const void*)(test)));

我们通过lldb命令,来看一下类中成员变量的存储顺序。(lldb命令的解释可以看下方 lldb命令 部分)

image.png

image.png

内存中的第一个8字节存储的是 isa ,然后第2个8字节存储的是 age ,第3个8字节存储的是 name ,第4个8字节存储的是 height

可见,内存中成员变量的存储顺序与属性定义顺序和赋值顺序都没有关系。原因是 苹果会自动重排属性成员变量的顺序来进行内存优化

isa一定在第一个8字节中

我们可以再看一个例子:

@interface TestClass : NSObject
// isa 8字节
@property (nonatomic, strong) NSString *name; // 指针 8字节
@property (nonatomic, assign) int age; // 4字节
@property (nonatomic, assign) double height; // 8字节
@property (nonatomic, assign) int number; // 4字节
// 8+8+4+8+4 = 32 16字节对齐后 = 32
@end


TestClass *test = [TestClass new];
test.number = 345;
test.height = 160.5;
test.age = 18;
test.name = @"hhhh";
NSLog(@"TestClass实例分配内存大小: %lu", malloc_size((__bridge const void*)(test)));

image.png

image.png

与第一个示例相比,多了一个属性,但是占用的内存大小没变。

成员变量在内存中的存储顺序

@interface TestClass : NSObject {
    @public
    // isa 
    NSString *name;
    int age; 
    double height; 
    int number;
}
TestClass *test = [TestClass new];
test->number = 345;
test->height = 160.5;
test->age = 18;
test->name = @"hhhh";
NSLog(@"TestClass实例分配内存大小: %lu", malloc_size((__bridge const void*)(test)));

打印信息可见,按同样顺序定义的成员变量,在内存中的排列顺序与定义顺序是一致的。成员变量并不会像属性一样进行自动重排image.png

lldb命令

ppo : 都是打印,p 打印的信息多一些

p/x : 打印十六进制数据

p/o :打印八进制数据

p/t :打印二进制数据

p/f :   打印浮点值

x : 打印对象的内存内容

x/ngxn表示打印个数,g表示每个打印为8字节, x表示以十六进制打印。例如以十六进制格式打印4个8字节内存内容是 x/4gx