实例对象
一个NSObject对象占用多少内存?
- 系统分配了16个字节给
NSObject
对象(通过malloc_size
函数获得) - 但
NSObject
对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize
函数获得)
创建一个实例对象,至少需要多少内存
#import <objc/runtime.h>
class_getInstanceSize([Student class])
创建一个实例对象,实际上分配了多少内存
#import <malloc/malloc.h>
malloc_size((__bridge const void *)stu)
我们平时编写的Objective-C代码,底层实现其实都是C\C++代码。所以Objective-C的面向对象都是基于C\C++的数据结构实现的。
image.pngObjective-C的对象、类主要是基于C\C++的 结构体 数据结构实现的。
将Objective-C代码转换为C\C++代码:
main.m
为源文件, main.cpp
为输出的CPP文件。
clang -rewrite-objc main.m -o main.cpp
不同平台支持的代码不同(Windows、mac、iOS)。
iOS平台。
- 模拟器:i386
- 32bit:armv7
- 64bit:arm64
main.m
为源文件, main.cpp
为输出的CPP文件。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
如果需要链接其他框架,使用-framework参数。比如 -framework UIKit
。
一个OC对象在内存中是如何布局的?
NSObject
的底层实现其实是一个结构体
@interface NSObject {
Class isa;
}
@end
struct NSObject_IMPL {
Class isa;
};
Class
是指向结构体的指针。typedef struct objc_class *Class;
。指针在64位占8个字节,32位占4个字节。
苹果源码地址:https://opensource.apple.com/tarballs/。在 objc4/
目录下下载源码,下载数值最大的也就是最新的。
class_getInstanceSize
源码:
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
instanceSize
方法中规定所有的对象至少16字节。
inline size_t instanceSize(size_t extraBytes) const {
if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
return cache.fastInstanceSize(extraBytes);
}
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
方法打印大小:
NSObject *obj = [[NSObject alloc] init];
// #import <objc/runtime.h>
// 获得NSObject实例对象的成员变量所占用的大小:8
NSLog(@"%zu", class_getInstanceSize([NSObject class]));
// #import <malloc/malloc.h>
// 获得obj指针所指向内存的大小:16
NSLog(@"%zu", malloc_size((__bridge const void *)(obj)));
也就是说创建一个 NSObject 对象会分配16个字节,但是实际利用起来的只有8个字节。
Student 类示例
@interface Student : NSObject
{
int _no;
int _age;
}
@end
@implementation Student
@end
Student
类其实是转成了 Student_IMPL
struct NSObject_IMPL {
Class isa;
};
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8个字节
int _no; // 4个字节
int _age; // 4个字节
};
Student *stu = [[Student alloc] init];
// 16
NSLog(@"%zu", class_getInstanceSize([Student class]));
// 16
NSLog(@"%zu", malloc_size((__bridge const void *)(stu)));
Person
对象和 Student
对象占用多少内存空间
- OC规定所有的对象至少16字节。
- 内存对齐:结构体的大小必须是最大成员大小的倍数。
@interface Person : NSObject
{
int _age;
}
@end
@implementation Person
@end
@interface Student : Person
{
int _no;
}
@end
@implementation Student
@end
struct NSObject_IMPL {
Class isa; // 8个字节
};
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8个字节
int _age; // 4个字节
};
struct Student_IMPL {
struct Person_IMPL Person_IVARS; // 16个字节
int _no; // 4个字节
};
Person_IMPL
占用 8+4=12
个字节,但是因为 OC规定所有的对象至少16字节 或者 内存对齐原则必须是8的倍数,所以分配16个字节。
Student_IMPL
因为 Person_IMPL
分配16个字节,但是 Person_IMPL
实际占用了 8+4=12
个字节,所以还剩4个字节,正好装下 _no
4个字节。所以也是分配16个字节。如果说 Student
再添加一个 int _height;
,那么 malloc_size
则变为32,也就是 Person_IMPL
16的倍数...
Person *p = [[Person alloc] init];
// 16
NSLog(@"%zu", class_getInstanceSize([Person class]));
// 16
NSLog(@"%zu", malloc_size((__bridge const void *)(p)));
Student *stu = [[Student alloc] init];
// 16
NSLog(@"%zu", class_getInstanceSize([Student class]));
// 16
NSLog(@"%zu", malloc_size((__bridge const void *)(stu)));
Person
对象占用多少内存空间
@interface Student : NSObject
{
int _age;
int _height;
int _no;
}
@end
@implementation Student
@end
struct NSObject_IMPL {
Class isa;
};
struct Student_IMPL {
struct NSObject_IMPL NSObject_IVARS; // 8
int _age; // 4
int _height; // 4
int _no; // 4
}; // 结构体需要 3*8=24 个字节
// 24
NSLog(@"%zu", sizeof(struct Student_IMPL));
Student *stu = [[Student alloc] init];
// 24
NSLog(@"%zu", class_getInstanceSize([Student class]));
// 32
NSLog(@"%zu", malloc_size((__bridge const void *)(stu)));
在这里,很意外的 Student
的 malloc_size
不是24,而是32。
苹果源码地址:https://opensource.apple.com/tarballs/。在 libmalloc/
目录下下载源码。
其实操作系统也有自己的对齐规则。内存中有一块块分配好的内存 NANO_MAX_SIZE
,大小为16的倍数,最大为256。
当 Student
需要24个字节大小的时候,操作系统会把原分配好的合适的内存分配给 Student
,也就是32。
#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, ..., 256} */
OC对象的分类
Objective-C中的对象,简称OC对象,主要可以分为3种
- instance对象(实例对象)
- class对象(类对象)
- meta-class对象(元类对象)
Class objc_getClass(const char *aClassName)
:
- 传入字符串类名
- 返回对应的了类对象
Class object_getClass(id obj)
:
-
obj
传入的值是 instance对象,返回class对象。 -
obj
传入的值是 class对象,返回meta-class对象。 -
obj
传入的值是 meta-class对象,返回 NSObject(基类)的meta-class对象
- (Class)class
、+ (Class)class
:
- 返回的就是类对象
instance对象(实例对象)
instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象。
NSObject *obj1 = [[NSObject alloc] init];
NSObject *obj2 = [[NSObject alloc] init];
- object1、object2是NSObject的instance对象(实例对象)
- 它们是不同的两个对象,分别占据着两块不同的内存
instance对象在内存中存储的信息包括:
- isa指针
- 其他成员变量
instance 的 isa 指向 class。当调用对象方法时,通过 instance 的 isa 找到 class,最后找到对象方法的实现进行调用。
class对象(类对象)
Class objectClass1 = [obj1 class];
Class objectClass2 = [obj2 class];
Class objectClass3 = object_getClass(obj1);
Class objectClass4 = object_getClass(obj2);
Class objectClass5 = [NSObject class];
Class objectClass6 = [[NSObject class] class];
Class objectClass7 = [[[NSObject class] class] class];
- objectClass1 ~ objectClass5都是NSObject的class对象(类对象)
- 它们是同一个对象。每个类在内存中有且只有一个class对象
class对象在内存中存储的信息主要包括
- isa指针
- superclass指针
- 类的属性信息(@property)
- 类的对象方法信息(instance method)
- 类的协议信息(protocol)
- 类的成员变量信息(ivar)
- ......
class 的 isa 指向 meta-class。当调用类方法时,通过 class 的 isa 找到 meta-class,最后找到类方法的实现进行调用
meta-class对象(元类对象)
Class objectMetaClass = object_getClass([NSObject class]);
- objectMetaClass是NSObject的meta-class对象(元类对象)
- 每个类在内存中有且只有一个meta-class对象
meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:
- isa指针
- superclass指针
- 类的类方法信息(class method)
- ......
isa指针
image.png对象的isa指针指向哪里?
- instance对象的isa指向class对象。当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用。
- class对象的isa指向meta-class对象。当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用。
- meta-class对象的isa指向基类的meta-class对象
@interface Person : NSObject
@end
@implementation Person
@end
@interface Student : Person
@end
@implementation Student
@end
class对象的superclass指针
当Student的instance对象要调用Person的对象方法时,会先通过isa找到Student的class,然后通过superclass找到 Person的class,最后找到对象方法的实现进行调用
meta-class对象的superclass指针
当Student的class要调用Person的类方法时,会先通过isa找到Student的meta-class,然后通过superclass找到Person的 meta-class,最后找到类方法的实现进行调用
总结
- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基类的meta-class
- class的superclass指向父类的class。如果没有父类,superclass指针为nil
- meta-class的superclass指向父类的meta-class。基类的meta-class的superclass指向基类的class
- instance调用对象方法的轨迹:isa找到class,方法不存在,就通过superclass找父类。
- class调用类方法的轨迹:isa找meta-class,方法不存在,就通过superclass找父类。
@interface Person : NSObject
@end
@implementation Person
@end
@interface Student : Person
@end
@implementation Student
@end
struct go_objc_class {
Class obj;
Class superclass;
};
Person *person = [[Person alloc] init];
Class personClass = [Person class];
Class studentClass = [Student class];
struct go_objc_class *pClass = (__bridge struct go_objc_class *)[Person class];
struct go_objc_class *sClass = (__bridge struct go_objc_class *)[Student class];
image.png
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
Person
实例对象的 isa
:0x011d800100008189
。
Person
类对象的地址:0x0000000100008188
。(0x0000000100008188
= 0x011d800100008189
& 0x00007ffffffffff8
)
sClass
类对象的 superclass
(sClass->superclass
) 等于 Person
类对象的地址值 (pClass
)。
从64bit开始,isa需要进行一次位运算,才能计算出真实地址。
image.pngobjc_class 的结构
struct objc_object {
private:
isa_t isa;
......
}
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // 方法缓存
class_data_bits_t bits; // 用于获取具体的类信息
class_rw_t *data() const { // 用于获取具体的类信息
return bits.data();
}
......
}
struct class_rw_t {
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
Class firstSubclass;
Class nextSiblingClass;
const class_ro_t *ro()
const method_array_t methods() // 方法列表
const property_array_t properties() // 属性列表
const protocol_array_t protocols() // 协议列表
......
};
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; // instance 对象占用的内存空间
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name; // 类名
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars; // 成员变量列表
......
};
OC的类信息存放在哪里?
- 对象方法、属性、成员变量、协议信息,存放在class对象中
- 类方法,存放在meta-class对象中
- 成员变量的具体值,存放在instance对象
网友评论