提到內(nèi)存管理在iOS開(kāi)發(fā)中陪毡,就不得不提ARC(自動(dòng)引用技術(shù))。本文主要討論的就是ARC在swift中是如何存儲(chǔ)下愈、計(jì)算,以及循環(huán)引用是如何解決的蕾久。
[toc]
一势似, refCount引用計(jì)數(shù)(強(qiáng)引用 + 無(wú)主引用)
先看一段簡(jiǎn)單的代碼
class classModel{
var age : Int = 18
}
func test() {
let c = classModel()
var c1 = c
var c2 = c
}
test()
通過(guò)LLDB
添加斷點(diǎn)查看當(dāng)前c
對(duì)象的內(nèi)存情況
- 通過(guò)經(jīng)驗(yàn)該對(duì)象的引用計(jì)數(shù)應(yīng)該是:3
- 可是
圖一
中對(duì)象內(nèi)存中refCopunt:0x0000000600000002
,以及通過(guò)cfGetRetainCount(AnyObject)獲取到的引用計(jì)算看起來(lái)都是不正確的僧著。
1. cfGetRetainCount - sil解析
class classModel{
var age : Int = 18
}
let temp = classModel()
CFGetRetainCount(temp)
編譯后的Sil文件:
- 通過(guò)
圖二
的sil
文件很簡(jiǎn)單的看出CFGetRetainCount
在調(diào)用之前對(duì)temp
這個(gè)變量進(jìn)行了一次強(qiáng)引用
履因,也就是引用計(jì)數(shù)加1
。所以通過(guò)CFGetRetainCount
獲得的引用計(jì)數(shù)需要-1
才是正確
的盹愚。這也印證了之前的經(jīng)驗(yàn)推論栅迄。
2. refCount - 類型的源碼
swift底層探索 01 - 類初始化&類結(jié)構(gòu)一文中有對(duì)swift類的源碼進(jìn)行過(guò)簡(jiǎn)單的解釋。
相信你一定會(huì)有疑惑:0x0000000600000002
是什么皆怕?它為什么被叫做refCount
霞篡,探索方法依舊是翻開(kāi)源碼!
- 由于源碼中涉及
多層嵌套+模板類+泛型
端逼,所以閱讀起來(lái)還是有點(diǎn)困難的朗兵,建議自己動(dòng)手試試。swift-5.3.1源碼地址
(1) 該方法是swift對(duì)象
的初始化方法
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
- 其中
refCounts(InlineRefCounts::Initialized)
就是refCounts
的初始化方法. -
InlineRefCounts
是refCounts
的類型.
(2) InlineRefCounts類型
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
- InlineRefCounts是重命名
InlineRefCounts = RefCounts
(3) RefCounts類
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
//省略方法
}
-
RefCounts
是依賴泛型:RefCountBits
的模板類顶滩。同時(shí)發(fā)現(xiàn)refCounts
的類型也是泛型:RefCountBits
余掖; - 通過(guò)第2步,第3步:
RefCounts = RefCountBits = InlineRefCountBits
(4) InlineRefCountBits類型
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
- InlineRefCountBits也是重命名
-
InlineRefCountBits = RefCountBitsT
;
(5) RefCountIsInline枚舉
enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };
- 傳入枚舉值:
RefCountIsInline = true
(6) RefCountBitsT 核心類
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
//內(nèi)部變量
BitsType bits;
//內(nèi)部變量類型
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
...
//省略無(wú)關(guān)代碼
}
- 內(nèi)部只有一個(gè)變量
bits
,類型為BitsType
(7) RefCountBitsInt 結(jié)構(gòu)體
template <RefCountInlinedness refcountIsInline>
struct RefCountBitsInt<refcountIsInline, 8> {
typedef uint64_t Type;
typedef int64_t SignedType;
};
- 根據(jù)第6步的傳參得到
RefCountBitsInt
結(jié)構(gòu),以及Type == uint64_t
(8) 【總結(jié)】
- 通過(guò)第1步,第2步,第3步,第4步:
InlineRefCounts = RefCounts = RefCountBits = InlineRefCountBits = RefCountBitsT
;(該關(guān)系并不嚴(yán)謹(jǐn)只是為了解釋簡(jiǎn)單) - 通過(guò)第6步,第7步:
RefCountBitsT
中bits
類型是:uint64_t
; -
refCounts
的類型為RefCountBitsT
,內(nèi)部只有一個(gè)變量bits
類型為uint64_t
; - 而
RefCountBitsT
是模板類,首地址指向唯一內(nèi)部變量bits
; -
結(jié)論為:
uint64_t : refCounts
.
3. refCount - 初始化的源碼
現(xiàn)在再看0x0000000600000002
知道它是一個(gè)uint64_t
的值,可是內(nèi)部存儲(chǔ)了哪些值還需要查看初始化方法
,觀察初始化方法做了什么礁鲁?
(1) 該方法是swift對(duì)象
的初始化方法
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
-
Initialized
初始化
(2) RefCounts
的初始化方法
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
enum Initialized_t { Initialized };
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
...
//省略無(wú)關(guān)代碼
}
- 調(diào)用了
RefCountBits
的初始化方法,根據(jù)上一步中的關(guān)系對(duì)應(yīng):RefCountBits = InlineRefCountBits = RefCountBitsT
(3) RefCountBitsT
的初始化方法
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
(BitsType(1) << Offsets::PureSwiftDeallocShift) |
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
{ }
(4)Offsets對(duì)應(yīng)關(guān)系
Offsets的關(guān)系圖:
(5)【總結(jié)】
-
0x0000000600000002
就可以拆分為: 5部分盐欺。強(qiáng)引用的引用計(jì)數(shù)位于:33-62
位
0x0000000600000002 >> 33 // 引用計(jì)數(shù) = 3
- 同樣滿足之前的論證。
補(bǔ)充1:
- 初始化并且沒(méi)有賦值時(shí)仅醇,
引用計(jì)數(shù)為0冗美,無(wú)主引用數(shù)為:1
。源碼中的確也是這樣的RefCountBits(0, 1)
補(bǔ)充2:
class PersonModel{
var age : Int = 18
}
func test() {
let c = PersonModel()
var c1 = c
var c2 = c
var c3 = c
//增加了一個(gè)無(wú)主引用
unowned var c4 = c
}
test()
-
unowned
在本文的解決循環(huán)引用中會(huì)解釋析二。 - StrongExtraRefCountShift(33-63位) :
0x0000000800000004
右移33位 =4
- UnownedRefCountShift(1-31位) :
0x0000000800000004
左移32位粉洼,右移33位。 =2
4. 引用計(jì)數(shù)增加叶摄、減少
知道了引用計(jì)數(shù)的數(shù)據(jù)結(jié)構(gòu)
和初始化值
属韧,現(xiàn)在就需要知道引用計(jì)數(shù)是如何增加
和減少
,本文中以增加為例蛤吓;
通過(guò)打開(kāi)匯編宵喂,查看調(diào)用堆棧:
- 發(fā)現(xiàn)會(huì)執(zhí)行
swift_retain
這個(gè)函數(shù)
swift_retain源碼
//入口函數(shù)
HeapObject *swift::swift_retain(HeapObject *object) {
CALL_IMPL(swift_retain, (object));
}
static HeapObject *_swift_retain_(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
if (isValidPointerForNativeRetain(object))
//引用計(jì)數(shù)在該函數(shù)進(jìn)行+1操作
object->refCounts.increment(1);
return object;
}
- 后面源碼的閱讀會(huì)進(jìn)行
斷點(diǎn)調(diào)試
的方式。
increment
通過(guò)可執(zhí)行源碼進(jìn)行調(diào)試可執(zhí)行源碼会傲。
- 根據(jù)斷點(diǎn)證實(shí)的確是執(zhí)行到
increment
函數(shù),并且新增值是1
具體計(jì)算的方法
- 計(jì)算都是從
33位
開(kāi)始計(jì)算的
二锅棕, refCount 循環(huán)引用
class PersonModel{
var teach : TeachModel?
}
class TeachModel{
var person : PersonModel?
}
面對(duì)這樣的相互包含的兩個(gè)類拙泽,使用時(shí)一定會(huì)出現(xiàn)相互引用(循環(huán)引用)
-
deinit
方法沒(méi)有調(diào)用,造成了循環(huán)引用裸燎。
1. weak關(guān)鍵字
通過(guò)OC的經(jīng)驗(yàn)奔滑,可以將其中一個(gè)值改為weak,就可以打破循環(huán)引用.
class PersonModel{
weak var teach : TeachModel?
}
class TeachModel{
weak var person : PersonModel?
}
- 很顯然
weak
是可以的顺少。問(wèn)題是:weak做了什么呢?
2. weak 實(shí)現(xiàn)源碼
weak var weakP = PersonModel()
依舊是打開(kāi)匯編斷點(diǎn)
.
- 從圖七能看出到weak是調(diào)用了
swift_weak
王浴。
swift_weak源碼
//weak入口函數(shù)
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
void nativeInit(HeapObject *object) {
//做一個(gè)非空判斷
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
- 沒(méi)有找到
WeakReference
對(duì)象的創(chuàng)建脆炎,猜測(cè)是編譯器自動(dòng)創(chuàng)建的用來(lái)管理weak動(dòng)作
.
通過(guò)formWeakReference創(chuàng)建HeapObjectSideTableEntry
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
auto side = allocateSideTable(true);
if (side)
return side->incrementWeak();
else
return nullptr;
}
調(diào)用allocateSideTable進(jìn)行創(chuàng)建
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
//獲取當(dāng)前對(duì)象的原本的引用計(jì)數(shù)(uInt64_t)
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
...
// FIXME: custom side table allocator
//創(chuàng)建HeapObjectSideTableEntry對(duì)象
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
//RefCountBitsT對(duì)象進(jìn)行初始化
auto newbits = InlineRefCountBits(side);
do {
if (oldbits.hasSideTable()) {
auto result = oldbits.getSideTable();
delete side;
return result;
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
return nullptr;
}
side->initRefCounts(oldbits);
//通過(guò)地址交換完成賦值
} while (! refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return side;
}
- 最終將
RefCountBitsT
對(duì)象(class)的地址和舊值uint_64
進(jìn)行交換。
HeapObjectSideTableEntry對(duì)象
class HeapObjectSideTableEntry {
std::atomic<HeapObject*> object;
SideTableRefCounts refCounts;
...
}
class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
//weak_count
uint32_t weakBits;
}
class RefCountBitsT {
//Uint64_t就是strong_count | unowned_count
BitsType bits;
}
通過(guò)源碼分析得出HeapObjectSideTableEntry
對(duì)象的內(nèi)存分布
RefCountBitsT初始化
最終保存到實(shí)例對(duì)象的refcount字段的內(nèi)容(RefCountBitsT)創(chuàng)建
//Offsets::SideTableUnusedLowBits = 3
//SideTableMarkShift 高位 62位
//UseSlowRCShift 高位 63位
RefCountBitsT(HeapObjectSideTableEntry* side)
: bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
| (BitsType(1) << Offsets::UseSlowRCShift)
| (BitsType(1) << Offsets::SideTableMarkShift))
{
assert(refcountIsInline);
}
-
62位氓辣,63位改為0 -> 整體左移3位
: 就可以得到sideTable對(duì)象
的地址秒裕。
lldb驗(yàn)證
現(xiàn)在知道了refcount字段獲取規(guī)律,以及sideTable對(duì)象
的內(nèi)部結(jié)構(gòu),現(xiàn)在通過(guò)lldb驗(yàn)證一下钞啸。
- 發(fā)現(xiàn)被weak修飾之后几蜻,refcount變化成
sideTable對(duì)象地址
+高位標(biāo)識(shí)符
- 將高位62,63變?yōu)?后体斩,在左移3位.
-
0x10325D870
這就是sideTable對(duì)象地址
weak_count 增加
weakcount是從第二位開(kāi)始計(jì)算的梭稚。
在formWeakReference
函數(shù)中出現(xiàn)了side->incrementWeak();
在sideTable對(duì)象
創(chuàng)建完成后調(diào)用了該函數(shù).
HeapObjectSideTableEntry* incrementWeak() {
if (refCounts.isDeiniting())
return nullptr;
//沒(méi)有銷毀就調(diào)用
refCounts.incrementWeak();
return this;
}
void incrementWeak() {
//獲取當(dāng)前的sideTable對(duì)象
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
RefCountBits newbits;
do {
newbits = oldbits;
assert(newbits.getWeakRefCount() != 0);
//調(diào)用核心自增函數(shù)
newbits.incrementWeakRefCount();
if (newbits.getWeakRefCount() < oldbits.getWeakRefCount())
swift_abortWeakRetainOverflow();
//通過(guò)值交換完成賦值
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_relaxed));
}
void incrementWeakRefCount() {
//就是一個(gè)簡(jiǎn)單++
weakBits++;
}
- 在聲明weak后,調(diào)用了
incrementWeak
自增方法絮吵; - 在
incrementWeak
方法中獲取了sideTable
對(duì)象弧烤; - 在
incrementWeakRefCount
完成了weakBits
的自增;
注:在weak引用之后蹬敲,在進(jìn)行strong強(qiáng)引用后暇昂,refCount該如何計(jì)算呢?篇幅問(wèn)題就不展開(kāi)了伴嗡,各位可以自己試試急波。
三, 捕獲列表
-
[weak t]
/[unowned t]
在swift中被稱為捕獲列表
瘪校。 - 作用:
- 解決closure的循環(huán)引用澄暮;
- 進(jìn)行外部變量的值捕獲
本次換個(gè)例子。
class TeachModel{
var age = 18
var closure : (() -> Void)?
deinit {
print("deinit")
}
}
func test() {
let b = TeachModel()
b.closure = {
b.age += 1
}
print("end")
}
- 看到這段代碼阱扬,deinit會(huì)不會(huì)執(zhí)行呢赏寇?答案是很顯然的,
實(shí)例對(duì)象的閉包和實(shí)例對(duì)象相互持有
价认,一定是不會(huì)釋放
的嗅定。
作用1-解決循環(huán)引用
func test() {
let b = TeachModel()
b.closure = {[weak b] in
b?.age += 1
}
print("end")
}
func test() {
let b = TeachModel()
b.closure = {[unowned b] in
b?.age += 1
}
print("end")
}
執(zhí)行效果,都可以解決循環(huán)引用:
- weak修飾之后對(duì)象會(huì)變?yōu)?/li>
作用2-捕獲外部變量
例如這樣的代碼:
func test() {
var age = 18
var height = 1.8
var name = "Henry"
height = 2.0
//age,height被閉包進(jìn)行了捕獲
let closure = {[age, height] in
print(age)
print(height)
print(name)
}
age = 20
height = 1.85
name = "Wan"
//猜猜會(huì)輸出什么用踩?
closure()
}
-
age
,height
被捕獲之后渠退,值雖然被外部修改但不會(huì)影響閉包內(nèi)的值
忙迁。 - 閉包捕獲的值時(shí)機(jī)為
閉包聲明之前
。
閉包捕獲之后值發(fā)生了什么碎乃?
通過(guò)打開(kāi)匯編調(diào)試,并查看寄存器堆棧信息.
- 猜測(cè)
rdx-0x0000000100507e00
姊扔,存在堆區(qū)。而閉包外的age
是存在棧區(qū)的梅誓。