跃迁引擎

空気を読んだ雨降らないでよ

iOS Research & Development


Dealloc的实现机制

今天来聊聊 Dealloc,它的实现机制是内存管理部分的重点,把这个知识点弄明白,对于全方位的理解 iOS 内存管理非常有帮助。本文将从源码的角度来解析 Dealloc 的实现机制。

本文源码可在下列位置中找到

  • runtime/objc-runtime-new.mm/
  • runtime/objc-object.h
  • runtime/NSObject.mm
  • runtime/objc-weak.mm

调用流程

调用流程图如下

Dealloc的调用流程概括来讲大概有5个基本步骤:

  • 首先调用 _objc_rootDealloc()

  • 接下来调用 rootDealloc()

  • 这时候会判断是否可以被释放,判断的依据主要有5个,判断是否有以下五种情况

    • NONPointer_ISA
    • weakly_reference
    • has_assoc
    • has_cxx_dtor
    • has_sidetable_rc
  • 如果有以上五中任意一种,将会调用 object_dispose()方法,做下一步的处理。如果没有之前五种情况的任意一种,则可以执行释放操作,C函数的 free()

  • 执行完毕。

1
2
3
- (void)dealloc {
_objc_rootDealloc(self);
}
1
2
3
4
5
6
7
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
19
20
21
inline void
objc_object::rootDealloc()
{
// TaggedPointer并不需要进行释放操作
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()
object_dispose((id)this);
}
}

object_dispose() 调用流程

  • 直接调用 objc_destructInstance()
  • 之后会调用 C函数的 free()进行释放。
1
2
3
4
5
6
7
8
9
10
11
id 
object_dispose(id obj)
{
if (!obj) return nil;
// 销毁实例,会对内部做一系列的析构操作
objc_destructInstance(obj);
// 析构完成后释放内存
free(obj);

return nil;
}

objc_destructInstance() 调用流程

  • 先判断 hasCxxDtor,如果有 C++ 的相关内容,要调用 object_cxxDestruct() ,销毁 C++ 相关的内容。
  • 再判断 hasAssocitatedObjects,如果有的话,要调用 object_remove_associations(),销毁关联对象的一系列操作。
  • 然后调用 clearDeallocating()
  • 执行完毕。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void *objc_destructInstance(id obj) 
{
// 该函数只销毁实例而不会释放内存
if (obj) {
// 一次性读取所有flags以提高性能。
bool cxx = obj->hasCxxDtor(); // 是否存在c++相关代码
bool assoc = obj->hasAssociatedObjects(); // 是否存在关联对象

// 下列执行的先后顺序很重要
if (cxx) object_cxxDestruct(obj); // 调用C++析构函数
if (assoc) _object_remove_assocations(obj); // 删除关联引用
obj->clearDeallocating(); // 调用ARC ivar清理
}

return obj;
}

clearDeallocating() 调用流程

  • 先执行 sideTable_clearDellocating()
  • 再执行 weak_clear_no_lock,在这一步骤中,会将指向该对象的弱引用指针置为 nil
  • 接下来执行 table.refcnts.eraser(),从引用计数表中擦除该对象的引用计数。
  • 至此为止,Dealloc 的执行流程结束。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
inline void 
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// 清除所有weak表 & 清除额外的保留计数并释放位
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// clearDeallocating()的慢路径, 用于含有 non-pointer isa 的对象
// 作用与sidetable_clearDeallocating()相同,都是清除weak引用和引用计数
clearDeallocating_slow();
}

assert(!sidetable_present());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void 
objc_object::sidetable_clearDeallocating()
{
SideTable& table = SideTables()[this];

//清除所有weak表项
//清除额外的保留计数并释放位
// (fixme warn or abort if extra retain count == 0 ?)
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
// 将指向该对象的弱引用指针置为nil
weak_clear_no_lock(&table.weak_table, (id)this);
}
// 从引用计数表中擦除该对象的引用计数
table.refcnts.erase(it);
}
table.unlock();
}
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
// 由dealloc调用; 取消所有指向该指针的弱指针
// 提供的对象,使其不再可以使用。
void
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;
}

// 弱引用指针
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) {
// 将指针置为nil
*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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));

SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
// 将指向该对象的弱引用指针置为nil
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
// 从引用计数表中擦除该对象的引用计数
table.refcnts.erase(this);
}
table.unlock();
}

调用时机

我们可以从sidetable_release函数的实现来窥出端倪,它会在给对象发送release消息的时候调用,sidetable_release方法首先获取对象的引用计数,对引用计数相关标志位做操作,若对象实例可以被释放,将通过objc_msgSend发送SEL_dealloc消息(调用对象的dealloc方法)。

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
uintptr_t
objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock();
// 获取对象的引用计数
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) {
do_dealloc = true;
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) { // it->second 的值的是引用计数减一
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second -= SIDE_TABLE_RC_ONE;
}
table.unlock();
// 调用dealloc
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}

从sidetable_release 源码中,我们可得知,其调用线程为最后一个调用release方法的线程,当需要释放对象时,向对象实例发送SEL_dealloc(即dealloc)消息。

也就是说,dealloc并不总是在主线程中被调用,它有可能在任何线程被调用,这就需要注意一点,就是在dealloc中进行UIKit相关API的操作(UIKit相关API只能在主线程操作)。

最近的文章

通告:之前网站无法访问,因为数据库出了问题,现已逐渐恢复

血的教训,数据库没来得及备份,Boom过后,寸草不生 …

, 开始阅读
更早的文章

iOS多线程总结(GCD、NSOperation、NSTread)

对 iOS 多线程技术 GCD、NSOperation、NSTread的一些总结。 …

开始阅读
comments powered by Disqus