本文主要通过三个问题,一起探寻一下OC对象实现的底层原理。
一个NSObject对象占用多少内存?
Objective-C的本质
我们平时编写的Objective-C代码,底层实现其实都是C\C++代码。
其实Objective-C的面向对象都是基于C\C++的结构体实现的。
我们可以通过下面的命令将Objective-C代码转换为C\C++代码来看一下具体的实现。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
OC代码
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *object = [[NSObject alloc] init];
}
return 0;
}
转换的C++文件
我们都知道每个OC对象都包含一个isa
指针,在NSObject的头文件中,我们可以看到如下代码:
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
typedef struct objc_class *Class;
在转换的C++代码中,我们也可以看到类似的结构体:
struct NSObject_IMPL {
Class isa;
};
struct NSArray_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
struct NSString_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
struct NSDictionary_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
不难猜想,struct NSObject_IMPL
其实就是NSObject的底层实现。
我们思考一下,一个OC对象在内存中是如何布局的?
NSObject其实就是结构体struct NSObject_IMPL
,包含了一个isa
指。在64位架构下,一个指针地址占用8个字节,是不是NSObjec对象所占用的内存就是8个字节呢?下面我们通过几种方法验证一下。
-
使用runtime里的
class_getInstanceSize ()
方法,获得NSObject实例对象的成员变量所占用的大小。
image.png -
使用
malloc_size()
,获得obj指针所指向内存的大小。
image.png
为什么两次结果会截然不同呢?我们看一下class_getInstanceSize
的具体实现。
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
ASSERT(isRealized());
return data()->ro()->instanceSize;
}
// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
return word_align(unalignedInstanceSize());
}
#ifdef __LP64__
# define WORD_SHIFT 3UL
# define WORD_MASK 7UL
# define WORD_BITS 64
#else
# define WORD_SHIFT 2UL
# define WORD_MASK 3UL
# define WORD_BITS 32
#endif
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t word_align(size_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
static inline size_t align16(size_t x) {
return (x + size_t(15)) & ~size_t(15);
}
从源码里我们可以看到,系统在计算分配内存的时进行了字节对齐。
64位系统下define WORD_MASK 7UL
假设环境是64位机器, 传进来的数字是2
return (2 + 7) & (~7);
return (0000 1001) & (~0000 0111);
return (0000 1001) & (1111 1000) = (0000 1000)
在计算机中, 位运算效率很高。
那为什么通过malloc_size ()
方法获取的是16个字节呢?
我们知道OC中创建一个对象需要调用alloc
方法,下面我们通过设定一个符号断电objc_alloc_init
看一下实际的调用堆栈。
通过调用堆栈信息,我们发现
alloc
方法会调用_objc_rootAllocWithZone
方法,下面我们看一下这个方法的具体实现:
NEVER_INLINE
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone __unused)
{
// allocWithZone under __OBJC2__ ignores the zone parameter
return _class_createInstanceFromZone(cls, 0, nil,
OBJECT_CONSTRUCT_CALL_BADALLOC);
}
/***********************************************************************
* class_createInstance
* fixme
* Locking: none
*
* Note: this function has been carefully written so that the fastpath
* takes no branch.
**********************************************************************/
static ALWAYS_INLINE id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
int construct_flags = OBJECT_CONSTRUCT_NONE,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
ASSERT(cls->isRealized());
// Read class's info bits all at once for performance
bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size;
size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;
id obj;
if (zone) {
obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (slowpath(!obj)) {
if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {
return _objc_callBadAllocHandler(cls);
}
return nil;
}
if (!zone && fast) {
obj->initInstanceIsa(cls, hasCxxDtor);
} else {
// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls);
}
if (fastpath(!hasCxxCtor)) {
return obj;
}
construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;
return object_cxxConstructFromClass(obj, cls, construct_flags);
}
我们发现,系统为对象开辟内存空间实际调用的是这个obj = (id)calloc(1, size);
,这里的size
是需要分配的内存的大小。
size = cls->instanceSize(extraBytes);
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;
}
通过代码,我们不难发现,CF框架内部规定:当创建一个实例对象的时候,为其分配的空间不能小于16个字节。
总结:
系统分配了16个字节给NSObject对象(通过malloc_size函数获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
对象的isa指针指向哪里?
OC的类信息存放在哪里?
OC对象的分类
Objective-C中的对象,简称OC对象,主要可以分为3种
- instance对象(实例对象)
- class对象(类对象)
- meta-class对象(元类对象)
instance
instance对象就是通过类alloc
出来的对象,每次调用alloc
都会产生新的instance对象。NSObject *obj = [[NSObject alloc] init];
instance对象在内存中存储的信息包括:
-
isa
指针 - 其他成员变量
class
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = object_getClass(object1);
Class objectClass4 = object_getClass(object2);
Class objectClass5 = [NSObject class];
objectClass1 ~ objectClass5都是NSObject的class对象(类对象),每个类在内存中有且只有一个class对象。
class对象在内存中存储的信息主要包括:
- isa指针
- superclass指针
- 类的属性信息(@property)、类的对象方法信息(instance method)
- 类的协议信息(protocol)、类的成员变量信息(ivar)
- ......
meta-class
Class objectMetaClass = object_getClass(objectClass5);
objectMetaClass是NSObject的meta-class对象(元类对象),每个类在内存中有且只有一个meta-class对象。
meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括:
- isa指针
- superclass指针
- 类的类方法信息(class method)
- ......
class_isMetaClass()
查看Class是否为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找父类
从64bit开始,isa需要进行一次位运算,才能计算出真实地址。
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
struct objc_class的结构
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
....
}
struct class_rw_ext_t {
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
char *demangledName;
uint32_t version;
};
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint16_t witness;
#if SUPPORT_INDEXED_ISA
uint16_t index;
#endif
explicit_atomic<uintptr_t> ro_or_rw_ext;
Class firstSubclass;
Class nextSiblingClass;
...
}
总结
对象的isa指针指向哪里?
instance对象的isa指向class对象
class对象的isa指向meta-class对象
meta-class对象的isa指向基类的meta-class对象
OC的类信息存放在哪里?
对象方法、属性、成员变量、协议信息,存放在class对象中
类方法,存放在meta-class对象中
成员变量的具体值,存放在instance对象