以下源码分析基于 objc4-781
对象的isa
初始化
在 +[NSObject alloc]
流程分析中,我们最终找到了对象创建的方法
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);
}
在上面的方法中主要做了3件事:
-
size = cls->instanceSize(extraBytes)
计算对象占用内存的大小。 -
obj = (id)calloc(1, size)
创建对象。 -
obj->initInstanceIsa(cls, hasCxxDtor)
初始化对象的isa
。
id
类型
调用 calloc
方法将结果强转成 id
类型,在源码中我们可以看到 id
类型的定义
// 定义在 Public Headers/objc.h
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
// 定义在 Project Headers/objc-private.h 中 line 82
struct objc_object {
isa_t isa;
}
/// A pointer to an instance of a class.
typedef struct objc_object *id;
我们可以在源码中找到2个版本的 struct objc_object
定义,一个在 Public Headers
目录下,另一个在 Project Headers
前者是暴露给开发者的,后者是内部使用的,所以 id
类型本质就是一个结构体指针,struct objc_object
只有一个成员变量 isa_t isa
。
调用 calloc
方法,只是从系统中申请了一片内存空间,这个时候这块内存空间中没有任何内容,通过 obj->initInstanceIsa
初始化对象中的 isa
。
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
initIsa(cls, true, hasCxxDtor);
}
在 initInstanceIsa
方法中调用了 initIsa(cls, true, hasCxxDtor)
inline void
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
if (!nonpointer) {
isa = isa_t((uintptr_t)cls);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
isa_t newisa(0);
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
}
在 intiIsa
方法中,主要就是对 isa
进行初始化。
isa_t
分析
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
};
union
联合体
我们发现 isa_t
是一个联合体,它和结构体有什么区别?
结构体
结构体是把不同的数据组合成一个整体,其变量是共存的,无论变量使用与否,都会分配内存。
结构体占用内存为其所有成员占用内存的总和。
- 优点:存储容量较大,包容性强,成员之间不会互相影响。
- 缺点:所有属性都会分配内存。
联合体
联合体也是由不同的数据类型组成的,但是其变量是互斥的,所有的成员占用同一段内存。而联合体采用了内存覆盖技术,同一时刻只能保存一个成员的值,如果对新的成员变量赋值,就会将原来的成员赋值。
联合体占用内存为其最大成员占用的内存。
- 优点:所有成员共用一段内存,使内存的使用更为精细灵活,同事也节省了内存空间。
- 缺点:包容性弱。
在 union isa_t
结构中,有三个成员 Class cls
uintptr_t bits
和 struct { ISA_BITFIELD; }
他们占用内存大小都是 8bytes
,因此整个联合体占用内存 8bytes
。
其中 struct { ISA_BITFIELD; }
是一个位域,用来描述当联合体的值是 bits
是每一位所代表的意义
ISA_BITFIELD
是一段宏,展开后如上图。他有两个版本, __arm64__
表示真机上位域的信息, __x86_64__
表示模拟器上的位域信息。
nonpointer
用来标识 isa
是不是一个单纯的 Class 指针
- 0:
isa
储存一个 Class 指针地址。 - 1:
isa
不仅包含了 Class 指针地址,还储存了一下其他的信息。
has_assoc
用来标识是不是又关联对象
- 0:没有关联对象
- 1:存在关联对象
has_cxx_dtor
标识对象有没有析构器,如果没有析构函数,可以更快的释放
- 0:没有析构器
- 1:存在析构器
shiftclx
用来储存指针地址,在真机环境中最大的指针地址为 0x1000000000
,转换成二进制为 1 0000 0000 0000 0000 0000 0000 0000 0000 0000
, 长度为37位, 且指针地址为8字节内存对齐,由于内存对齐的原因实际上 0x1000000000
也不是有效的地址,低三位都为0,所以指针的实际有效位数为33位。同样的在模拟器中最大的指针地址为 0x7fffffe00000
对应二进制47位, 除去低三位,有效的指针位数为44位。
magic
用于调试器判断当前对象是真正的对象,还是没有初始化的空间。
weakly_refrenced
表示对象是否指向或者曾经指向一个ARC的弱引用变量。
deallocating
表示对象是否正在被释放。
has_sidetable_rc
判断该对象的引用对象是否过大,如果过大则需要其他散列表来进行存储。
extra_rc
存放该对象的引用计数减1后的结果,如果引用计数超过1,会存在这个里面,如果引用计数为10,extra_rc
的值就为9。