对象的本质
新建一个 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命令
部分)
内存中的第一个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)));
与第一个示例相比,多了一个属性,但是占用的内存大小没变。
成员变量在内存中的存储顺序
@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)));
打印信息可见,按同样顺序定义的成员变量,在内存中的排列顺序与定义顺序是一致的。成员变量并不会像属性一样进行自动重排。
lldb命令
p
和 po
: 都是打印,p
打印的信息多一些
p/x
: 打印十六进制数据
p/o
:打印八进制数据
p/t
:打印二进制数据
p/f
: 打印浮点值
x
: 打印对象的内存内容
x/ngx
:n
表示打印个数,g
表示每个打印为8字节, x
表示以十六进制打印。例如以十六进制格式打印4个8字节内存内容是 x/4gx