前言
- 上一篇我们分析了alloc&init&New的实现,创建一个对象实际上返回了一个id类型的obj,今天让我们来剖析这个obj。
目录
简介
今天我们来从以下3个问题入手来探究OC对象:
- OC对象是什么
- OC对象包含什么
- 如何计算OC对象所占内存大小
OC对象是什么?
上一篇中我们分析alloc最终返回了一个id类型的obj。
从源码入手分析obj,objc源码中的object.mm
typedef struct objc_class *Class;
typedef struct objc_object *id;
objc.h
/// Represents an instance of a class.
// objc_object 的声明
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
objc-private.h:
// objc_object 的实现
struct objc_object {
private:
isa_t isa;
以下是一些函数,省略
...
}
// isa_t的定义,一个联合体,值为cls或者bits
union isa_t {
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
#endif
};
其中uintptr_t是一个unsigned long类型,定义如下
typedef unsigned long uintptr_t;
objc-runtime-new.h :
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
以下是一些函数,省略
...
}
- 结合源码我们知道,OC对象是objc_object的结构体,包含了一个联合体类型的成员isa,isa的值可能是cls或者bits,cls是指针类型,占8字节,bits是unsigned long类型,占8字节,由此得到isa占大小为8字节。
- 另外根据源码,我也知道了类是一个objc_class的结构体,objc_class继承自objc_object,说明类也是一个对象。
OC对象包含什么
我们已经知道了OC对象是一个objc_object类型的结构体,里面包含一个联合体isa,那么isa究竟是什么?
- 关于isa_t的具体分析,我在C语言位域详解和实例分析中的isa_t位域分析里有详细介绍,大家可以参考一下,里面对**位域 **也进行了详细的介绍。
- 总结概述,isa_t联合体有3个成员,3个成员cls\bit\匿名结构体s共同占用8字节的内存空间,8个字节空间存储着对象、类等的一些信息,通过匿名结构体里面的位域,可以获取到这些信息。
对象所占的内存空间
关于对象所占的内存空间,严格上讲是对象的指针所指向的结构体所占的内存空间。
系统提供了2个api获取有关对象内存空间的大小,我们看看如下例子:
创建Person继承NSObject:
Person.h
@interface Person : NSObject
@end
Person.m
@implementation Person
@end
我们在main.m中添加
Person *person = [Person alloc];
FHLog(@"class_getInstanceSize: is %ld ",class_getInstanceSize([person class]));
FHLog(@"malloc_size:%ld ",malloc_size((__bridge const void*)person));
log打印如下:
class_getInstanceSize: is 8
malloc_size:16
class_getInstanceSize和malloc_size究竟代表什么呢?
- class_getInstanceSize输出的是对象实例经过内存对齐后的占用的大小,
- malloc_size输出的是对象实际占用内存空间的大小,即系统实际分配给对象的内存空间的大小。
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() {
return word_align(unalignedInstanceSize());
}
// 相当于(x+7)>>3 <<3,保证>=8且按8的倍数对齐
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
# define WORD_MASK 7UL
// May be unaligned depending on class's ivars.
// 根据类的实例变量可能会进行内存对齐处理
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;
}
- 这里说明一下data()->ro->instanceSize,data()是objc_class的bits.data(),data()->ro->instanceSize的大小的在编译的时候就已经确定了,和类中成员变量以及对齐系数有关,默认是8的倍数,具体涉及到了内存对齐的知识。关于内存对齐,请看带你深入理解iOS-内存对齐。
- 由于unalignedInstanceSize() 可能受对齐系数的影响,得到的值可能不是8的倍数,需要word_align()保证返回的值为8的倍数。
- 我们的Person虽然没定义成员变量,但是Person继承自NSObject,实际上是一个objc_Objec类型的结构体,但是里面包含一个联合体类型的isa,isa前面我们分析,所占大小为8字节,所以输出class_getInstanceSize: is 8。
malloc_size
- malloc_size返回的是指针所指向的内存空间所占的大小,即系统实际分配的大小。
在第一篇我们分析alloc流程时候,在_class_createInstanceFromZone里有
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
.....
// 1.根据extraBytes计算对象的内存空间大小
size_t size = cls->instanceSize(extraBytes);
···
// 2.根据计算的size为obj申请分配内存
obj = (id)calloc(1, size);
.....
}
其中cls->instanceSize实现如下:
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
- extraBytes通常为0,cls->instanceSize返回值按8字节对齐,并且保证最小值为16,所以size最小为16。
- 调用calloc函数向系统申请分配内存,calloc的对输入的size按16字节对齐方式开辟内存空间开辟大小:size+15>>4<<4,保证size>=16且为16的倍数(calloc的源码实现在libmalloc项目里,这里暂不深入libmalloc里中calloc源码实现)。
- 由于Person只包含一个联合体isa,因此malloc_size:16。
我们为改动Person测试如下:
Person.h
@interface Person : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
@end
输出:
class_getInstanceSize: is 16
malloc_size:16
Person.h
@interface Person : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
@property (nonatomic, assign) bool isHealthy;
@end
输出:
class_getInstanceSize: is 24
malloc_size:32
与我们的分析一致。
- 总结:OC中的继承自NSObject的对象所占内存空间大小,首先根据成员变量和内存对齐规则,得到实例变量所占的空间大小size,然后调用calloc输入size,按16字节对齐最后得到实际开辟的内存空间的大小。
总结
- OC对象是什么?
答:OC对象是一个objc_object类型的结构体 - OC对象包含什么?
答:OC对象是一个objc_object类型的结构体,包含一个联合体类型的isa - OC对象所占内存大小的的计算实现?
答:OC对象实际占用内存大小(即系统分配的内存大小),先根据对象的成员属性按8字节计算得到对象成员对齐大小(alignmentSize),再把alignmentSize按照16字节对齐计算得到对象分配内存大小(allocSize)。
思考
- 为什么要按为对象分配内存空间按16字节对齐?
从提高寻址效率和内存空间综合考虑。