Swift源碼解析之弱引用
序言:
各個(gè)社區(qū)有關(guān) Objective-C weak 機(jī)制的實(shí)現(xiàn)分析文章有很多,然而 Swift 發(fā)布這么長(zhǎng)時(shí)間以來,有關(guān) ABI 的分析文章一直非常少,似乎也是很多 iOS 開發(fā)者未涉及的領(lǐng)域… 本文就從源碼層面分析一下 Swift 是如何實(shí)現(xiàn) weak 機(jī)制的。
下面話不多說了,來一起看看詳細(xì)的介紹吧
準(zhǔn)備工作
由于 Swift 源碼量較大,強(qiáng)烈建議大家把 repo clone 下來,結(jié)合源碼一起來看這篇文章。
$ git clone https://github.com/apple/swift.git
Swift 整個(gè)工程采用了 CMake 作為構(gòu)建工具,如果你想用 Xcode 來打開的話需要先安裝 LLVM,然后用 cmake -G 生成 Xcode 項(xiàng)目。
我們這里只是進(jìn)行源碼分析,我就直接用 Visual Studio Code 配合 C/C++ 插件了,同樣支持符號(hào)跳轉(zhuǎn)、查找引用。另外提醒一下大家,Swift stdlib 里 C++ 代碼的類型層次比較復(fù)雜,不使用 IDE 輔助閱讀起來會(huì)相當(dāng)費(fèi)勁。
正文
下面我們就正式進(jìn)入源碼分析階段,首先我們來看一下 Swift 中的對(duì)象(class 實(shí)例)它的內(nèi)存布局是怎樣的。
HeapObject
我們知道 Objective-C 在 runtime 中通過 objc_object 來表示一個(gè)對(duì)象,這些類型定義了對(duì)象在內(nèi)存中頭部的結(jié)構(gòu)。同樣的,在 Swift 中也有類似的結(jié)構(gòu),那就是 HeapObject,我們來看一下它的定義:
struct HeapObject { /// This is always a valid pointer to a metadata object. HeapMetadata const *metadata; SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS; HeapObject() = default; // Initialize a HeapObject header as appropriate for a newly-allocated object. constexpr HeapObject(HeapMetadata const *newMetadata) : metadata(newMetadata) , refCounts(InlineRefCounts::Initialized) { } // Initialize a HeapObject header for an immortal object constexpr HeapObject(HeapMetadata const *newMetadata, InlineRefCounts::Immortal_t immortal) : metadata(newMetadata) , refCounts(InlineRefCounts::Immortal) { } };
可以看到,HeapObject 的第一個(gè)字段是一個(gè) HeapMetadata 對(duì)象,這個(gè)對(duì)象有著與 isa_t 類似的作用,就是用來描述對(duì)象類型的(等價(jià)于 type(of:) 取得的結(jié)果),只不過 Swift 在很多情況下并不會(huì)用到它,比如靜態(tài)方法派發(fā)等等。
接下來是 SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS,這是一個(gè)宏定義,展開后即:
RefCounts<InlineRefCountBits> refCounts;
這是一個(gè)相當(dāng)重要東西,引用計(jì)數(shù)、弱引用、unowned 引用都與它有關(guān),同時(shí)它也是 Swift 對(duì)象(文中后續(xù)的 Swift 對(duì)象均指引用類型,即 class 的實(shí)例)中較為復(fù)雜的一個(gè)結(jié)構(gòu)。
其實(shí)說復(fù)雜也并不是很復(fù)雜,我們知道 Objective-C runtime 里就有很多 union 結(jié)構(gòu)的應(yīng)用,例如 isa_t 有 pointer 類型也有 nonpointer 類型,它們都占用了相同的內(nèi)存空間,這樣做的好處就是能更高效地使用內(nèi)存,尤其是這些大量使用到的東西,可以大大減少運(yùn)行期的開銷。類似的技術(shù)在 JVM 里也有,就如對(duì)象頭的 mark word。當(dāng)然,Swift ABI 中也大量采用這種技術(shù)。
RefCounts 類型和 Side Table
上面說到 RefCounts 類型,這里我們就來看看它到底是個(gè)什么東西。
先看一下定義:
template <typename RefCountBits> class RefCounts { std::atomic<RefCountBits> refCounts; // ... };
這就是 RefCounts 的內(nèi)存布局,我這里省略了所有的方法和類型定義。你可以把 RefCounts 想象成一個(gè)線程安全的 wrapper,模板參數(shù) RefCountBits 指定了真實(shí)的內(nèi)部類型,在 Swift ABI 里總共有兩種:
typedef RefCounts<InlineRefCountBits> InlineRefCounts; typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
前者是用在 HeapObject 中的,而后者是用在 HeapObjectSideTableEntry(Side Table)中的,這兩種類型后文我會(huì)一一講到。
一般來講,Swift 對(duì)象并不會(huì)用到 Side Table,一旦對(duì)象被 weak 或 unowned 引用,該對(duì)象就會(huì)分配一個(gè) Side Table。
InlineRefCountBits
定義:
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits; 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; // ... };
通過模板替換之后,InlineRefCountBits 實(shí)際上就是一個(gè) uint64_t,相關(guān)的一堆類型就是為了通過模板元編程讓代碼可讀性更高(或者更低,哈哈哈)。
下面我們來模擬一下對(duì)象引用計(jì)數(shù) +1:
調(diào)用 SIL 接口 swift::swift_retain:
HeapObject *swift::swift_retain(HeapObject *object) { return _swift_retain(object); } static HeapObject *_swift_retain_(HeapObject *object) { SWIFT_RT_TRACK_INVOCATION(object, swift_retain); if (isValidPointerForNativeRetain(object)) object->refCounts.increment(1); return object; } auto swift::_swift_retain = _swift_retain_;
調(diào)用 RefCounts 的 increment 方法:
void increment(uint32_t inc = 1) { // 3. 原子地讀出 InlineRefCountBits 對(duì)象(即一個(gè) uint64_t)。 auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME); RefCountBits newbits; do { newbits = oldbits; // 4. 調(diào)用 InlineRefCountBits 的 incrementStrongExtraRefCount 方法 // 對(duì)這個(gè) uint64_t 進(jìn)行一系列運(yùn)算。 bool fast = newbits.incrementStrongExtraRefCount(inc); // 無 weak、unowned 引用時(shí)一般不會(huì)進(jìn)入。 if (SWIFT_UNLIKELY(!fast)) { if (oldbits.isImmortal()) return; return incrementSlow(oldbits, inc); } // 5. 通過 CAS 將運(yùn)算后的 uint64_t 設(shè)置回去。 } while (!refCounts.compare_exchange_weak(oldbits, newbits, std::memory_order_relaxed)); }
到這里就完成了一次 retain 操作。
SideTableRefCountBits
上面是不存在 weak、unowned 引用的情況,現(xiàn)在我們來看看增加一個(gè) weak 引用會(huì)怎樣。
- 調(diào)用 SIL 接口 swift::swift_weakAssign(暫時(shí)省略這塊的邏輯,它屬于引用者的邏輯,我們現(xiàn)在先分析被引用者)
- 調(diào)用 RefCounts<InlineRefCountBits>::formWeakReference 增加一個(gè)弱引用:
template <> HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference() { // 分配一個(gè) Side Table。 auto side = allocateSideTable(true); if (side) // 增加一個(gè)弱引用。 return side->incrementWeak(); else return nullptr; }
重點(diǎn)來看一下 allocateSideTable 的實(shí)現(xiàn):
template <> HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting) { auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME); // 已有 Side Table 或正在析構(gòu)就直接返回。 if (oldbits.hasSideTable()) { return oldbits.getSideTable(); } else if (failIfDeiniting && oldbits.getIsDeiniting()) { return nullptr; } // 分配 Side Table 對(duì)象。 HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject()); auto newbits = InlineRefCountBits(side); do { if (oldbits.hasSideTable()) { // 此時(shí)可能其他線程創(chuàng)建了 Side Table,刪除該線程分配的,然后返回。 auto result = oldbits.getSideTable(); delete side; return result; } else if (failIfDeiniting && oldbits.getIsDeiniting()) { return nullptr; } // 用當(dāng)前的 InlineRefCountBits 初始化 Side Table。 side->initRefCounts(oldbits); // 進(jìn)行 CAS。 } while (! refCounts.compare_exchange_weak(oldbits, newbits, std::memory_order_release, std::memory_order_relaxed)); return side; }
還記得 HeapObject 里的 RefCounts 實(shí)際上是 InlineRefCountBits 的一個(gè) wrapper 嗎?上面構(gòu)造完 Side Table 以后,對(duì)象中的 InlineRefCountBits 就不是原來的引用計(jì)數(shù)了,而是一個(gè)指向 Side Table 的指針,然而由于它們實(shí)際都是 uint64_t,因此需要一個(gè)方法來區(qū)分。區(qū)分的方法我們可以來看 InlineRefCountBits 的構(gòu)造函數(shù):
LLVM_ATTRIBUTE_ALWAYS_INLINE RefCountBitsT(HeapObjectSideTableEntry* side) : bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits) | (BitsType(1) << Offsets::UseSlowRCShift) | (BitsType(1) << Offsets::SideTableMarkShift)) { assert(refcountIsInline); }
其實(shí)還是最常見的方法,把指針地址無用的位替換成標(biāo)識(shí)位。
順便,看一下 Side Table 的結(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() { } // ... };
此時(shí)再增加引用計(jì)數(shù)會(huì)怎樣呢?來看下之前的 RefCounts::increment 方法:
void increment(uint32_t inc = 1) { auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME); RefCountBits newbits; do { newbits = oldbits; bool fast = newbits.incrementStrongExtraRefCount(inc); // ---> 這次進(jìn)入這個(gè)分支。 if (SWIFT_UNLIKELY(!fast)) { if (oldbits.isImmortal()) return; return incrementSlow(oldbits, inc); } } while (!refCounts.compare_exchange_weak(oldbits, newbits, std::memory_order_relaxed)); } template <typename RefCountBits> void RefCounts<RefCountBits>::incrementSlow(RefCountBits oldbits, uint32_t n) { if (oldbits.isImmortal()) { return; } else if (oldbits.hasSideTable()) { auto side = oldbits.getSideTable(); // ---> 然后調(diào)用到這里。 side->incrementStrong(n); } else { swift::swift_abortRetainOverflow(); } } void HeapObjectSideTableEntry::incrementStrong(uint32_t inc) { // 最終到這里,refCounts 是一個(gè) RefCounts<SideTableRefCountBits> 對(duì)象。 refCounts.increment(inc); }
到這里我們就需要引出 SideTableRefCountBits 了,它與前面的 InlineRefCountBits 很像,只不過又多了一個(gè)字段,看一下定義:
class SideTableRefCountBits : public RefCountBitsT<RefCountNotInline> { uint32_t weakBits; // ... };
小結(jié)一下
不知道上面的內(nèi)容大家看暈了沒有,反正我一開始分析的時(shí)候費(fèi)了點(diǎn)時(shí)間。
上面我們講了兩種 RefCounts,一種是 inline 的,用在 HeapObject 中,它其實(shí)是一個(gè) uint64_t,可以當(dāng)引用計(jì)數(shù)也可以當(dāng) Side Table 的指針。
Side Table 是一種類名為 HeapObjectSideTableEntry 的結(jié)構(gòu),里面也有 RefCounts 成員,是內(nèi)部是 SideTableRefCountBits,其實(shí)就是原來的 uint64_t 加上一個(gè)存儲(chǔ)弱引用數(shù)的 uint32_t。
WeakReference
上面說的都是被引用的對(duì)象所涉及的邏輯,而引用者這邊的邏輯就稍微簡(jiǎn)單一些了,主要就是通過 WeakReference 這個(gè)類來實(shí)現(xiàn)的,比較簡(jiǎn)單,我們簡(jiǎn)單過一下就行。
Swift 中的 weak 變量經(jīng)過 silgen 之后都會(huì)變成 swift::swift_weakAssign 調(diào)用,然后派發(fā)給 WeakReference::nativeAssign:
void nativeAssign(HeapObject *newObject) { if (newObject) { assert(objectUsesNativeSwiftReferenceCounting(newObject) && "weak assign native with non-native new object"); } // 讓被引用者構(gòu)造 Side Table。 auto newSide = newObject ? newObject->refCounts.formWeakReference() : nullptr; auto newBits = WeakReferenceBits(newSide); // 喜聞樂見的 CAS。 auto oldBits = nativeValue.load(std::memory_order_relaxed); nativeValue.store(newBits, std::memory_order_relaxed); assert(oldBits.isNativeOrNull() && "weak assign native with non-native old object"); // 銷毀原來對(duì)象的弱引用。 destroyOldNativeBits(oldBits); }
弱引用的訪問就更簡(jiǎn)單了:
HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) { auto side = bits.getNativeOrNull(); return side ? side->tryRetain() : nullptr; }
到這里大家發(fā)現(xiàn)一個(gè)問題沒有,被引用對(duì)象釋放了為什么還能直接訪問 Side Table?其實(shí) Swift ABI 中 Side Table 的生命周期與對(duì)象是分離的,當(dāng)強(qiáng)引用計(jì)數(shù)為 0 時(shí),只有 HeapObject 被釋放了。
只有所有的 weak 引用者都被釋放了或相關(guān)變量被置 nil 后,Side Table 才能得以釋放,相見:
void HeapObjectSideTableEntry::decrementWeak() { // FIXME: assertions // FIXME: optimize barriers bool cleanup = refCounts.decrementWeakShouldCleanUp(); if (!cleanup) return; // Weak ref count is now zero. Delete the side table entry. // FREED -> DEAD assert(refCounts.getUnownedCount() == 0); delete this; }
所以即便使用了弱引用,也不能保證相關(guān)內(nèi)存全部被釋放,因?yàn)橹灰?weak 變量不被顯式置 nil,Side Table 就會(huì)存在。而 ABI 中也有可以提升的地方,那就是如果訪問弱引用變量時(shí)發(fā)現(xiàn)被引用對(duì)象已經(jīng)釋放,就將自己的弱引用銷毀掉,避免之后重復(fù)無意義的 CAS 操作。當(dāng)然 ABI 不做這個(gè)優(yōu)化,我們也可以在 Swift 代碼里做。:)
總結(jié)
以上就是 Swift 弱引用機(jī)制實(shí)現(xiàn)方式的一個(gè)簡(jiǎn)單的分析,可見思路與 Objective-C runtime 還是很類似的,都采用與對(duì)象匹配的 Side Table 來維護(hù)引用計(jì)數(shù)。不同的地方就是 Objective-C 對(duì)象在內(nèi)存布局中沒有 Side Table 指針,而是通過一個(gè)全局的 StripedMap 來維護(hù)對(duì)象和 Side Table 之間的關(guān)系,效率沒有 Swift 這么高。另外 Objective-C runtime 在對(duì)象釋放時(shí)會(huì)將所有的 __weak 變量都 zero-out,而 Swift 并沒有。
總的來說,Swift 的實(shí)現(xiàn)方式會(huì)稍微簡(jiǎn)單一些(雖然代碼更復(fù)雜,Swift 團(tuán)隊(duì)追求更高的抽象)。第一次分析 Swift ABI,本文僅供參考,如果存在錯(cuò)誤,歡迎大家勘正。感謝!
好了,以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對(duì)我們的支持。
上一篇:深入講解Swift中的模式匹配
欄 目:Swift
下一篇:Swift仿選擇電影票的效果并實(shí)現(xiàn)無限/自動(dòng)輪播的方法
本文標(biāo)題:Swift源碼解析之弱引用
本文地址:http://mengdiqiu.com.cn/a1/Swift/11935.html
您可能感興趣的文章
- 01-11swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景詳解
- 01-11Swift利用Decodable解析JSON的一個(gè)小問題詳解
- 01-11Swift中defer關(guān)鍵字推遲執(zhí)行示例詳解
- 01-11Swift中初始化init的方法小結(jié)
- 01-11Swift中定義單例的方法實(shí)例
- 01-11Swift利用純代碼實(shí)現(xiàn)時(shí)鐘效果實(shí)例代碼
- 01-11Swift中排序算法的簡(jiǎn)單取舍詳解
- 01-11Swift如何為設(shè)置中心添加常用功能
- 01-11Swift Json實(shí)例詳細(xì)解析
- 01-11Swift利用指紋識(shí)別或面部識(shí)別為應(yīng)用添加私密保護(hù)功能


閱讀排行
- 1C語言 while語句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹的示例代碼(圣誕
- 3利用C語言實(shí)現(xiàn)“百馬百擔(dān)”問題方法
- 4C語言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-11Swift利用Decodable解析JSON的一個(gè)小問題
- 01-11swift中defer幾個(gè)簡(jiǎn)單的使用場(chǎng)景詳解
- 01-11Swift中初始化init的方法小結(jié)
- 01-11Swift中defer關(guān)鍵字推遲執(zhí)行示例詳解
- 01-11Swift利用純代碼實(shí)現(xiàn)時(shí)鐘效果實(shí)例代碼
- 01-11Swift中定義單例的方法實(shí)例
- 01-11Swift中排序算法的簡(jiǎn)單取舍詳解
- 01-11Swift Json實(shí)例詳細(xì)解析
- 01-11Swift如何為設(shè)置中心添加常用功能
- 01-11Swift利用指紋識(shí)別或面部識(shí)別為應(yīng)用添
隨機(jī)閱讀
- 01-10C#中split用法實(shí)例總結(jié)
- 01-11ajax實(shí)現(xiàn)頁面的局部加載
- 04-02jquery與jsp,用jquery
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-10使用C語言求解撲克牌的順子及n個(gè)骰子
- 01-10SublimeText編譯C開發(fā)環(huán)境設(shè)置
- 01-10delphi制作wav文件的方法
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什