Swift底層原理-內(nèi)存管理
Swift
語言延續(xù)了和Objective-C
語言一樣的思路進(jìn)行內(nèi)存管理,都是采用引用計數(shù)
的方式來管理實例的內(nèi)存空間琢蛤;在結(jié)構(gòu)體與類中我們了解到
Swift
對象本質(zhì)是一個HeapObject
結(jié)構(gòu)體指針敷硅。HeapObject
結(jié)構(gòu)中有兩個成員變量映胁,metadata
和refCounts
峭弟,metadata
是指向元數(shù)據(jù)對象的指針,里面存儲著類的信息弦撩,比如屬性信息,虛函數(shù)表等论皆。而refCounts
通過名稱可以知道益楼,它是一個引用計數(shù)信息相關(guān)的東西。接下來我們研究一下refCounts
点晴。
refCounts
- 在源碼
HeapObject.h
文件中感凤,我們可以找到HeapObject
結(jié)構(gòu)體中關(guān)于refCounts
的定義
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
/// This is always a valid pointer to a metadata object.
HeapMetadata const *metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
// 省略部分代碼
}
- 我們看到
refCounts
的類型為InlineRefCounts
,在RefCount.h
文件中找到InlineRefCounts
的定義:
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
RefCounts
- 發(fā)現(xiàn)
InlineRefCounts
是一個模版類:RefCounts
粒督,接收一個InlineRefCountBits
類型的范型
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
// Out-of-line slow paths.
LLVM_ATTRIBUTE_NOINLINE
void incrementSlow(RefCountBits oldbits, uint32_t inc) SWIFT_CC(PreserveMost);
LLVM_ATTRIBUTE_NOINLINE
void incrementNonAtomicSlow(RefCountBits oldbits, uint32_t inc);
LLVM_ATTRIBUTE_NOINLINE
bool tryIncrementSlow(RefCountBits oldbits);
LLVM_ATTRIBUTE_NOINLINE
bool tryIncrementNonAtomicSlow(RefCountBits oldbits);
LLVM_ATTRIBUTE_NOINLINE
void incrementUnownedSlow(uint32_t inc);
public:
enum Initialized_t { Initialized };
enum Immortal_t { Immortal };
// 省略部分方法
}
- 根據(jù)
RefCounts
的定義我們發(fā)現(xiàn)陪竿,其實質(zhì)上是在操作我們傳遞的泛型參數(shù)InlineRefCountBits
- 我們看一下
InlineRefCountBits
的定義
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
- 它也是一個模板函數(shù),并且也有一個參數(shù)
RefCountIsInline
屠橄,而RefCountIsInline
其實就是true
族跛。我們重點看一下RefCountBitsT
的結(jié)構(gòu)
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
friend class RefCountBitsT<RefCountIsInline>;
friend class RefCountBitsT<RefCountNotInline>;
static const RefCountInlinedness Inlinedness = refcountIsInline;
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::SignedType
SignedBitsType;
typedef RefCountBitOffsets<sizeof(BitsType)>
Offsets;
BitsType bits;
// 省略部分代碼
}
- 在
RefCountBitsT
中,發(fā)現(xiàn)只有一個bits
屬性锐墙,而該屬性是由RefCountBitsInt
的Type
屬性定義的礁哄; - 我們來看一下
RefCountBitsInt
的結(jié)構(gòu):
template <RefCountInlinedness refcountIsInline>
struct RefCountBitsInt<refcountIsInline, 8> {
typedef uint64_t Type;
typedef int64_t SignedType;
};
- 可以看到,
Type
的類型是一個uint64_t
的位域信息溪北,在這個uint64_t
的位域信息中存儲著運行生命周期的相關(guān)引用計數(shù)桐绒。
引用計數(shù)初始化流程
- 我們創(chuàng)建一個新的實例對象時夺脾,他的引用計數(shù)是多少呢?從源碼中我們找到
HeapObject
的初始化方法:
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
assert(isAlignmentMask(requiredAlignmentMask));
auto object = reinterpret_cast<HeapObject *>(
swift_slowAlloc(requiredSize, requiredAlignmentMask));
// NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
// check on the placement new allocator which we have observed on Windows,
// Linux, and macOS.
new (object) HeapObject(metadata);
// If leak tracking is enabled, start tracking this object.
SWIFT_LEAKS_START_TRACKING_OBJECT(object);
SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);
return object;
}
- 調(diào)用了
HeapObject
初始化方法
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
- 給
refCounts
賦值了Initialized
茉继,我們繼續(xù)分析發(fā)現(xiàn)Initialized
是一個枚舉類型Initialized_t
enum Initialized_t { Initialized };
enum Immortal_t { Immortal };
// RefCounts must be trivially constructible to avoid ObjC++
// destruction overhead at runtime. Use RefCounts(Initialized)
// to produce an initialized instance.
RefCounts() = default;
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
- 而根據(jù)注釋得知劳翰,一個新的實例被創(chuàng)建時,傳入的是
RefCountBits(0馒疹,1)
佳簸,并且我們可以看到refCounts
函數(shù)的參數(shù)傳的不就是前面提到RefCountBitsT
類型參數(shù),我們找到RefCountBitsT
初始化方法
LLVM_ATTRIBUTE_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
(BitsType(1) << Offsets::PureSwiftDeallocShift) |
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
{ }
- 已知外部調(diào)用
RefCountBitsT
初始化方法颖变,strongExtraCount
傳 0生均,unownedCount
傳 1。 - 然后我們?nèi)ゲ榭磶讉€偏移的定義
# define shiftAfterField(name) (name##Shift + name##BitCount)
template <>
struct RefCountBitOffsets<8> {
static const size_t PureSwiftDeallocShift = 0;
static const size_t PureSwiftDeallocBitCount = 1;
static const uint64_t PureSwiftDeallocMask = maskForField(PureSwiftDealloc);
static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
static const size_t UnownedRefCountBitCount = 31;
static const uint64_t UnownedRefCountMask = maskForField(UnownedRefCount);
static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
static const size_t StrongExtraRefCountBitCount = 30;
static const uint64_t StrongExtraRefCountMask = maskForField(StrongExtraRefCount);
// 結(jié)果分析
StrongExtraRefCountShift = shiftAfterField(IsDeiniting)
= IsDeinitingShift + IsDeinitingBitCount
= shiftAfterField(UnownedRefCount) + 1
= UnownedRefCountShift + UnownedRefCountBitCount + 1
= shiftAfterField(PureSwiftDealloc) + 31 + 1
= PureSwiftDeallocShift + PureSwiftDeallocBitCount + 31 + 1
= 0 + 1 + 31 + 1 = 33
}
- 通過上面計算得到:
Offsets::StrongExtraRefCountShift
= 33腥刹,Offsets::PureSwiftDeallocShift
= 0马胧,Offsets::UnownedRefCountShift
= 1 - 知道了這三個值的之后,我們開始計算
RefCountBitsT
的初始化方法調(diào)用bits
的值:
0 << 33 | 1 << 0 | 1 << 1;
0 | 1 | 2 = 3;
- 最終
bits
存儲信息如下:
[圖片上傳失敗...(image-b8859d-1670758320398)]
- 第
0
位:標(biāo)識是否是永久的 - 第
1-31
位:存儲無主引用 - 第
32
位:標(biāo)識當(dāng)前類是否正在析構(gòu) - 第
33-62
位:標(biāo)識強引用 - 第
63
位:是否使用SlowRC
強引用
默認(rèn)情況下衔峰,引用都是強引用佩脊。通過前面對
refCounts
的結(jié)構(gòu)分析,得知它是存儲引用計數(shù)信息的東西垫卤,在創(chuàng)建一個對象之后它的初始值為0x0000000000000003
威彰。如果我對這個實例對象進(jìn)行多個引用,引用計數(shù)會增加穴肘,那這個強引用是如何添加的歇盼?
底層會通過調(diào)用
_swift_retain_
方法
static HeapObject *_swift_retain_(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
if (isValidPointerForNativeRetain(object))
object->refCounts.increment(1);
return object;
}
- 在進(jìn)行強引用的時候,本質(zhì)上是調(diào)用
refCounts
的increment
方法评抚,也就是引用計數(shù) +1豹缀。我們來看一下increment
的實現(xiàn):
void increment(uint32_t inc = 1) {
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// constant propagation will remove this in swift_retain, it should only
// be present in swift_retain_n
if (inc != 1 && oldbits.isImmortal(true)) {
return;
}
RefCountBits newbits;
do {
newbits = oldbits;
bool fast = newbits.incrementStrongExtraRefCount(inc);
if (SWIFT_UNLIKELY(!fast)) {
if (oldbits.isImmortal(false))
return;
return incrementSlow(oldbits, inc);
}
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_relaxed));
}
- 在
increment
中調(diào)用了incrementStrongExtraRefCount
,我們再去看看incrementStrongExtraRefCount
實現(xiàn)
LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
bool incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}
- 此時
inc
為1
慨代,StrongExtraRefCountShift
根據(jù)之前的計算為33
-
1 << 33
為結(jié)果為8589934592
,其對應(yīng)的十六進(jìn)制為0x200000000
- 到這里就實現(xiàn)了引用計數(shù)
+1
的操作
弱引用
- 在實際開發(fā)的過程中邢笙,我們大多使用的都是強引用,在某些場景下使用強引用侍匙,用不好的話會造成循環(huán)引用氮惯。
- 在
Swift
中我們通過關(guān)鍵字weak
來表明一個弱引用;weak
關(guān)鍵字的作用是在使用這個實例
的時候并不保有此實例的引用丈积。使用weak
關(guān)鍵字修飾的引用類型數(shù)據(jù)在傳遞時不會使引用計數(shù)加1
筐骇,不會對其引用的實例保持強引用,因此不會阻止ARC
釋放被引用的實例江滨。 - 由于弱引用不會保持對實例的引用铛纬,所以當(dāng)實例被釋放的時候,弱引用仍舊引用著這個實例也是有可能唬滑。因此告唆,
ARC
會在被引用的實例釋放時棺弊,自動地將弱引用設(shè)置為nil
。由于弱引用
需要允許設(shè)置為nil
擒悬,因此它一定是可選類型
模她;
swift_weakInit
- 用
weak
修飾之后,變量變成了一個可選項懂牧,并且侈净,還會調(diào)用一個swift_weakInit
函數(shù)
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
- 發(fā)現(xiàn)用
weak
修飾之后,在內(nèi)部會生成WeakReference
類型的變量僧凤,并在swift_weakInit
中調(diào)用nativeInit
函數(shù)畜侦。
void nativeInit(HeapObject *object) {
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
- 在
nativeInit
方法中調(diào)用了formWeakReference()
方法,也就意味著形成了弱引用
(形成一個散列表):
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
auto side = allocateSideTable(true);
if (side)
return side->incrementWeak();
else
return nullptr;
}
- 它本質(zhì)就是創(chuàng)建了一個散列表
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
// 去除原有的refCount躯保,也是是64位信息
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// Preflight failures before allocating a new side table.
// 判斷原來的 refCounts 是否有當(dāng)前的引用計數(shù)
if (oldbits.hasSideTable()) {
// 如果有直接返回
return oldbits.getSideTable();
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// 如果沒有并且正在析構(gòu)直接返回 nil
return nullptr;
}
// Preflight passed. Allocate a side table.
// 創(chuàng)建一個散列表
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
auto newbits = InlineRefCountBits(side);
// 對原來的散列表以及正在析構(gòu)的一些處理
do {
if (oldbits.hasSideTable()) {
// Already have a side table. Return it and delete ours.
// Read before delete to streamline barriers.
auto result = oldbits.getSideTable();
delete side;
return result;
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
side->initRefCounts(oldbits);
} while (! refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return side;
}
- 散列表的創(chuàng)建可以分為4步:
- 取出原來的
refCounts
引用計數(shù)的信息旋膳。 - 判斷原來的
refCounts
是否有散列表,如果有直接返回途事,如果沒有并且正在析構(gòu)直接返回nil
验懊。 - 創(chuàng)建一個散列表。
- 對原來的散列表以及正在析構(gòu)的一些處理尸变。
- 取出原來的
HeapObjectSideTableEntry
- 接下來我們來看看這個散列表
HeapObjectSideTableEntry
Storage layout:
HeapObject {
isa
InlineRefCounts {
atomic<InlineRefCountBits> {
strong RC + unowned RC + flags
OR
HeapObjectSideTableEntry*
}
}
}
HeapObjectSideTableEntry {
SideTableRefCounts {
object pointer
atomic<SideTableRefCountBits> {
strong RC + unowned RC + weak RC + flags
}
}
}
- 可以分析出在
Swift
中本質(zhì)上存在兩種引用計數(shù):- 如果是
強引用
义图,那么是strong RC + unowned RC + flags
; - 如果是
弱引用
振惰,那么是HeapObjectSideTableEntry
歌溉;
- 如果是
- 我們看一下
HeapObjectSideTableEntry
結(jié)構(gòu)
class HeapObjectSideTableEntry {
// FIXME: does object need to be atomic?
std::atomic<HeapObject*> object;
SideTableRefCounts refCounts;
public:
HeapObjectSideTableEntry(HeapObject *newObject)
: object(newObject), refCounts()
{ }
// 省略部分代碼
}
- 可以看到,
HeapObjectSideTableEntry
中存著對象的指針骑晶,并且還有一個refCounts
,而refCounts
的類型為SideTableRefCounts
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
-
SideTableRefCountBits
就是繼承自我們前面學(xué)過的RefCountBitsT
的模版類
class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
uint32_t weakBits;
public:
LLVM_ATTRIBUTE_ALWAYS_INLINE
SideTableRefCountBits() = default;
}
- 它多了一個
weakBits
成員變量草慧。 - 所以
HeapObjectSideTableEntry
里邊存儲的是64位
原有的strong RC + unowned RC + flags
桶蛔,再加上32位
的weak RC
; - 當(dāng)我們用
weak
修飾之后漫谷,這個散列表就會存儲對象的指針和引用計數(shù)信息相關(guān)的東西仔雷。
無主引用
在
Swift
中可以通過unowned
定義無主引用,unowned
不會產(chǎn)生強引用舔示,實例銷毀后仍然存儲著實例的內(nèi)存地址(類似于OC
中的unsafe_unretained
)碟婆。需要注意的是試圖在實例銷毀后訪問無主引用,會產(chǎn)生運行時錯誤(野指針)惕稻。-
weak
竖共、unowned
都能解決循環(huán)引用的問題,unowned
要比weak
少一些性能消耗俺祠,那我們?nèi)绾蝸磉x擇weak
和unowned
呢?- 如果強引用的雙方生命周期沒有任何關(guān)系公给,使用
weak
- 如果其中一個對象銷毀借帘,另一個對象也跟著銷毀,則使用
unowned
淌铐;
- 如果強引用的雙方生命周期沒有任何關(guān)系公给,使用
weak
相對于unowned
更兼容肺然,更安全,而unowned
性能更高腿准;這是因為weak
需要操作散列表
际起,而unowned
只需要操作64
位位域信息;在使用unowned
的時候吐葱,要確保其修飾的屬性一定有值加叁。