KAOLA's note.

对象的引用计数是如何管理的

字数统计: 1.8k阅读时长: 8 min
2019/05/06 Share

问题

  1. iOS如何管理内存
  2. 对象的引用计数是如何管理的
    • 对象的引用计数保存在哪里
    • 对象的引用计数是如何修改的
      iOS采用自动释放池技术和ARC技术管理堆内存 iOS内存分区
    • 栈区:
    • 堆区:
    • 全局区:
    • 文字常量区
    • 程序代码区

      ARC

      ARC的全称是AutoReleaseConference自动引用计数 区别于MRC,程序员不需要手动释放,
      • 对于实例变量,编译器会在合适的位置插入内存管理语句
      • 对于临时变量,采用自动释放池技术管理对象的引用计数

autoreleasepool

autoreleasepool也就是前文提到的自动释放池技术,详情可参考自动释放池的原理

管理对象的引用计数

问题

对象的生命周期

  • 生成&持有对象(rc+1)objc initialize(alloc/new/copy/mutableCopy)
  • 持有对象(rc+1)objc retain
  • 释放对象(rc-1)objc release
  • 废弃对象(rc==0)objc dealloc

  • 前面提到的关于引用计数的主要问题是:对象的引用计数保存在哪里对象的引用计数是如何修改的
    查看objc的源码(我的版本是723,现在有更新的)

  • 找到对象生成、持有、释放时候调用的api

    先说结论 对象的引用计数=初始化时1+引用计数器中extra_rc+sideTable中相应的redcnts中的rc

    生成&持有对象

    1
    2
    3
    4
     objc_alloc(Class cls)
    {
    return callAlloc(cls, true/*checkNil*/, false/*allocWithZone*/);
    }

    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
     
    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];
    }

    class_createInstance创建实例细节

    1
    2
    3
    4
    5
    6
     id 
    class_createInstance(Class cls, size_t extraBytes)
    {
    return _class_createInstanceFromZone(cls, extraBytes, nil);
    }
    ...

    并没有看到操作引用计数的地方

持有对象 retain

生成对象的时候提到相关代码中并没有找到操作引用计数的地方(真的,我已经用了control+F大法了,没找到)
retain的调用栈比较好认,直接看

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
51
52
53
54
55
56
57
58
objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
if (isTaggedPointer()) return (id)this;// line 472 判断是否是taggedPointer
bool sideTableLocked = false;//sideTable 还没有开始操作 没上锁
bool transcribeToSideTable = false;//是否记录到SideTable中

isa_t oldisa;// 旧的 isa_t的结构可以点进去看 跟以前的isa指针不完全一样
isa_t newisa;

do {
transcribeToSideTable = false;
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
//普通的isa slowpath是一个优化 slowpath后面的内容表示不是大概率事件(也就是slowpath跟着的是低概率事件) ps:nonpointer表示isa不是以前那个纯粹的isa
//普通的isa 直接处理sideTable里的内容
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();
}
// don't check newisa.fast_rr; we already called any RR overrides
if (slowpath(tryRetain && newisa.deallocating)) {// 正在销毁
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
// 继续往下走 isa采用了新的结构
uintptr_t carry;
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++
// newisa中的extra_rc计数器++


if (slowpath(carry)) {//newisa.extra_rc溢出了
// newisa.extra_rc++ overflowed
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
// Leave half of the retain counts inline and
// prepare to copy the other half to the side table.
//留一半 然后另一半存到sideTable中
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;//有rc转移到sideTable中了
newisa.extra_rc = RC_HALF;//
newisa.has_sidetable_rc = true;//这个设置为true 表示我们
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));//替换并更新

if (slowpath(transcribeToSideTable)) {//如果有转移 将一半存到sideTable中
// Copy the other half of the retain counts to the side table.
sidetable_addExtraRC_nolock(RC_HALF);
}

if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}

也就是说,我们每次操作的是isa_t中的extra_rc++ 当这个结构中的extra_rc溢出,我们才把一半计数转移到sideTable中,而不是我之前理解的extra_rc不够用了再放到sideTable中。
我最开始认为的做法有哪些坏处呢?
真正的做法好处有哪些?
我想到的

  1. 访问sideTable需要hash计算

释放对象 release

代码有些长 一点一点看

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;//常规操作

bool sideTableLocked = false;

isa_t oldisa;
isa_t newisa;

retry:
do {
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {//判断是不是nonpointer
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return sidetable_release(performDealloc);
}
//继续往下走 是nonpointer 我们先操作extra_rc
// don't check newisa.fast_rr; we already called any RR overrides
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {//有没有出现下溢出
// don't ClearExclusive()
goto underflow;//执行underflow部分的代码
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));//操作完之后更新isa

if (slowpath(sideTableLocked)) sidetable_unlock();
return false;

underflow:
// newisa.extra_rc-- underflowed: borrow from side table or deallocate
//出现下溢 检查sideTable中有没有 没有的话 就需要释放了

// abandon newisa to undo the decrement
newisa = oldisa;

if (slowpath(newisa.has_sidetable_rc)) {//这个值记录了有没有把rc放到sideTable中
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}

// Transfer retain count from side table to inline storage.

if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
// Need to start over to avoid a race against
// the nonpointer -> raw pointer transition.
goto retry;
}

// Try to remove some retain counts from the side table.
//把之前借走的一部分拿回来 拿这么多RC_HALF 直接借的时候每次都借这么多
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

// To avoid races, has_sidetable_rc must remain set
// even if the side table count is now zero.
// has_sidetable_rc这个还不能改

if (borrowed > 0) {
// Side table retain count decreased.
// Try to add them to the inline count.
newisa.extra_rc = borrowed - 1; // redo the original
decrement too
//拿回来 并减1
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
// Inline update failed.
// Try it again right now. This prevents livelock on LL/SC
// architectures where the side table access itself may have
// dropped the reservation.
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}

if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
//如果操作失败了 放回sideTable并重试
}

// Decrement successful after borrowing from side table.
// This decrement cannot be the deallocating decrement - the side
// table lock and has_sidetable_rc bit ensure that if everyone
// else tried to -release while we worked, the last one would block.
sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}

// Really deallocate.
//如果没有被借 就真的要释放了

if (slowpath(newisa.deallocating)) {//这个字段表示是否正在释放
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;//重试

if (slowpath(sideTableLocked)) sidetable_unlock();//解锁

__sync_synchronize();
if (performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}

总结一下

  1. isa的类型决定了引用计数加一减一是否直接操作sideTable
  2. 对象初始化的时候不操作引用计数
  3. 引用计数为0的时候,调用dealloc
  4. 对象
CATALOG
  1. 1. 问题
    1. 1.1. ARC
    2. 1.2. autoreleasepool
    3. 1.3. 管理对象的引用计数
      1. 1.3.1. 问题
      2. 1.3.2. 对象的生命周期
        1. 1.3.2.1. 先说结论 对象的引用计数=初始化时1+引用计数器中extra_rc+sideTable中相应的redcnts中的rc
        2. 1.3.2.2. 生成&持有对象
        3. 1.3.2.3. 持有对象 retain
        4. 1.3.2.4. 释放对象 release
      3. 1.3.3. 总结一下