NSObject源码分析

本篇文章分析使用的 runtime 为 objc4-756.2

NSObject 是什么?

对于 objc 来说,一个对象可以想象成为一个结构体。至少他们在内存布局时是这样的。接下来我们来证实一下。首先我们来看下NSObject头文件的定义。

1
2
3
4
5
typedef struct objc_class *Class;

@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}

可以看到定义为内部有一个类型为objc_class的结构体指针isa。然后新建一个工程,在main.m中写下如下代码

1
2
3
4
5
6
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *object = [[NSObject alloc] init];
}
return 0;
}

通过clang将其编译成为cpp文件。

1
clang -rewrite-objc main.m -o main.cpp

打开main.cpp可以看到这样一个结构体

1
2
3
struct NSObject_IMPL {
Class isa;
};

有没有发现和NSObject头文件的定义很像。其实这个就是NSObject的结构。下面用一个例子来证明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
struct Person_IMPL {
Class isa;
int _no;
int _age;
};
@interface Person : NSObject
{
@public
int _no;
int _age;
}
@end

@implementation Person

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *per = [[Person alloc] init];
per->_no = 4;
per->_age = 5;
struct Person_IMPL *perImpl = (__bridge struct Person_IMPL *)per;
NSLog(@"no is %d, age is %d", perImpl->_no, perImpl->_age);
}
return 0;
}

代码过程为创建了一个Person类。将其成员变量赋值。将创建好的Person对象桥接成为Person_IMPL结构体。通过访问结构体成员输出log来看。对象就是结构体。

NSObject 是如何创建的

相信大家大家一定都使用过alloc。用来给一个对象分配内存空间。那么它内部到底做了什么呢?参考Runtime源码中NSObject.mm这个文件。

1
2
3
4
5
6
7
8
9
10
11
+ (id)alloc {
return _objc_rootAlloc(self);
}

// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}

通过查看源码可以发现alloc调用了_objc_rootAlloc。而_objc_rootAlloc调用了callAlloc。接下来我们来分析callAlloc做了什么。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate 
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;

#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize());
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif

// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}

callAlloc第二个参数checkNil入参为false,因此此函数不会返回nil。后续的创建就不一步步跟了,想要了解的同学可以自己查看源码。总之。最后他们都会回到一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
static __attribute__((always_inline)) 
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
if (!cls) return nil;

assert(cls->isRealized());

// Read class's info bits all at once for performance

// 类或者父类是否有构造函数
bool hasCxxCtor = cls->hasCxxCtor();

// 类或者父类是否有析构函数
bool hasCxxDtor = cls->hasCxxDtor();

// 是对 isa 的类型的区分,如果一个类和它父类的实例不能使用isa_t 类型的 isa 的话,fast 就为 false,但是在 Objective-C 2.0 中,大部分类都是支持的
bool fast = cls->canAllocNonpointer();

// 计算分配的内存大小 不足16 补齐16 (依系统位数而定)
size_t size = cls->instanceSize(extraBytes);
if (outAllocatedSize) *outAllocatedSize = size;

id obj;
if (!zone && fast) {
obj = (id)calloc(1, size);
if (!obj) return nil;
obj->initInstanceIsa(cls, hasCxxDtor); // 初始化Isa指针
}
else {
if (zone) {
obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);
} else {
obj = (id)calloc(1, size);
}
if (!obj) return nil;

// Use raw pointer isa on the assumption that they might be
// doing something weird with the zone or RR.
obj->initIsa(cls); // 初始化Isa指针
}

if (cxxConstruct && hasCxxCtor) {
obj = _objc_constructOrFree(obj, cls);
}

return obj;
}

初始化isa解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor)
{
assert(!isTaggedPointer());

if (!nonpointer) { //现在基本不会走到这里了 标识是否是优化过的指针
isa.cls = 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; //设置这个 主要是设置 magic 和 nonpointer
// 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;
}
}

初始化的过程就是对isa_t结构体初始化的过程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD
uintptr_t nonpointer : 1; // 0 表示普通的 isa 指针,1 表示使用优化,存储引用计数
uintptr_t has_assoc : 1; // 表示该对象是否包含 associated object,如果没有,则析构时会更快
uintptr_t has_cxx_dtor : 1; // 表示该对象是否有 C++ 或 ARC 的析构函数,如果没有,则析构时更快
uintptr_t shiftcls : 33; // 类的指针
uintptr_t magic : 6; // 固定值为 0xd2,用于在调试时分辨对象是否未完成初始化。
uintptr_t weakly_referenced : 1; // 表示该对象是否有过 weak 对象,如果没有,则析构时更快
uintptr_t deallocating : 1; // 表示该对象是否正在析构
uintptr_t has_sidetable_rc : 1; // 表示该对象的引用计数值是否过大无法存储在 isa 指针
uintptr_t extra_rc : 19; // 存储引用计数值减一后的结果
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)

Init 做了什么事情?

1
2
3
4
5
6
7
8
9
10
11
- (id)init {
return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
// In practice, it will be hard to rely on this function.
// Many classes do not properly chain -init calls.
return obj;
}

可以看到,对于NSObject来说init没有做任何事情。通过函数名可以看出alloc是用于分配内存。而init是用于初始化。而NSObject不需要额外的初始化操作。所以就直接返回了自己。

对象是如何销毁的

1
2
3
4
5
6
7
8
9
10
11
- (void)dealloc {
_objc_rootDealloc(self);
}

void
_objc_rootDealloc(id obj)
{
assert(obj);

obj->rootDealloc();
}

最终会调用到这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?

if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}

isTaggedPointer可以看这篇文章科普,这里就不详细介绍了 深入理解Tagged Pointer

indexed是代表是否开启isa指针优化。weakly_referenced代表对象被指向或者曾经指向一个 ARC 的弱变量。has_assoc代表对象含有或者曾经含有关联引用。has_cxx_dtor之前提到过了,是析构器。has_sidetable_rc判断该对象的引用计数是否过大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
id 
object_dispose(id obj)
{
if (!obj) return nil;

objc_destructInstance(obj);
free(obj);

return nil;
}

void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();

// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}

cxx表示该对象是否有析构函数,如果没有析构器就会快速释放内存。前面有说道过。
assoc标识是否有关联属性(分类新增成员变量),

object_cxxDestruct实现和原理比较复杂。可以看 ARC下dealloc过程及.cxx_destruct的探究 这篇文章说的非常棒。总结一下。结论是

  • ARC下对象的成员变量于编译器插入的.cxx_desctruct方法自动释放
  • ARC下[super dealloc]方法也由编译器自动插入
  • 所谓编译器插入代码过程需要进一步了解,还不清楚其运作方式
  • clang的CodeGen也值得深入研究一下

_object_remove_assocations后面会在runtime的文章中介绍。主要适用于解除关联属性的。

接下来说下clearDeallocating

1
2
3
4
5
6
7
8
9
10
11
12
13
14
inline void 
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}

assert(!sidetable_present());
}

主要有两个函数就不一一贴上来了。功能是相同的。最终都会调用weak_clear_no_lock。都是拿到弱引用表。进行擦除操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
{
objc_object *referent = (objc_object *)referent_id;

weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}

// zero out references
weak_referrer_t *referrers;
size_t count;

if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}

for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}

weak_entry_remove(weak_table, entry);
}