欧美大屁股bbbbxxxx,狼人大香伊蕉国产www亚洲,男ji大巴进入女人的视频小说,男人把ji大巴放进女人免费视频,免费情侣作爱视频

歡迎來到入門教程網(wǎng)!

Swift

當(dāng)前位置:主頁 > 軟件編程 > Swift >

Swift源碼解析之弱引用

來源:本站原創(chuàng)|時間:2020-01-11|欄目:Swift|點擊: 次

序言:

各個社區(qū)有關(guān) Objective-C weak 機制的實現(xiàn)分析文章有很多,然而 Swift 發(fā)布這么長時間以來,有關(guān) ABI 的分析文章一直非常少,似乎也是很多 iOS 開發(fā)者未涉及的領(lǐng)域… 本文就從源碼層面分析一下 Swift 是如何實現(xiàn) weak 機制的。

下面話不多說了,來一起看看詳細(xì)的介紹吧

準(zhǔn)備工作

由于 Swift 源碼量較大,強烈建議大家把 repo clone 下來,結(jié)合源碼一起來看這篇文章。

$ git clone https://github.com/apple/swift.git

Swift 整個工程采用了 CMake 作為構(gòu)建工具,如果你想用 Xcode 來打開的話需要先安裝 LLVM,然后用 cmake -G 生成 Xcode 項目。

我們這里只是進行源碼分析,我就直接用 Visual Studio Code 配合 C/C++ 插件了,同樣支持符號跳轉(zhuǎn)、查找引用。另外提醒一下大家,Swift stdlib 里 C++ 代碼的類型層次比較復(fù)雜,不使用 IDE 輔助閱讀起來會相當(dāng)費勁。

正文

下面我們就正式進入源碼分析階段,首先我們來看一下 Swift 中的對象(class 實例)它的內(nèi)存布局是怎樣的。

HeapObject

我們知道 Objective-C 在 runtime 中通過 objc_object 來表示一個對象,這些類型定義了對象在內(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 的第一個字段是一個 HeapMetadata 對象,這個對象有著與 isa_t 類似的作用,就是用來描述對象類型的(等價于 type(of:) 取得的結(jié)果),只不過 Swift 在很多情況下并不會用到它,比如靜態(tài)方法派發(fā)等等。

接下來是 SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS,這是一個宏定義,展開后即:

RefCounts<InlineRefCountBits> refCounts;

這是一個相當(dāng)重要東西,引用計數(shù)、弱引用、unowned 引用都與它有關(guān),同時它也是 Swift 對象(文中后續(xù)的 Swift 對象均指引用類型,即 class 的實例)中較為復(fù)雜的一個結(jié)構(gòu)。

其實說復(fù)雜也并不是很復(fù)雜,我們知道 Objective-C runtime 里就有很多 union 結(jié)構(gòu)的應(yīng)用,例如 isa_t 有 pointer 類型也有 nonpointer 類型,它們都占用了相同的內(nèi)存空間,這樣做的好處就是能更高效地使用內(nèi)存,尤其是這些大量使用到的東西,可以大大減少運行期的開銷。類似的技術(shù)在 JVM 里也有,就如對象頭的 mark word。當(dāng)然,Swift ABI 中也大量采用這種技術(shù)。

RefCounts 類型和 Side Table

上面說到 RefCounts 類型,這里我們就來看看它到底是個什么東西。

先看一下定義:

template <typename RefCountBits>
class RefCounts {
 std::atomic<RefCountBits> refCounts;

 // ...

};

這就是 RefCounts 的內(nèi)存布局,我這里省略了所有的方法和類型定義。你可以把 RefCounts 想象成一個線程安全的 wrapper,模板參數(shù) RefCountBits 指定了真實的內(nèi)部類型,在 Swift ABI 里總共有兩種:

typedef RefCounts<InlineRefCountBits> InlineRefCounts;
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;

前者是用在 HeapObject 中的,而后者是用在 HeapObjectSideTableEntry(Side Table)中的,這兩種類型后文我會一一講到。

一般來講,Swift 對象并不會用到 Side Table,一旦對象被 weak 或 unowned 引用,該對象就會分配一個 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 實際上就是一個 uint64_t,相關(guān)的一堆類型就是為了通過模板元編程讓代碼可讀性更高(或者更低,哈哈哈)。

下面我們來模擬一下對象引用計數(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 對象(即一個 uint64_t)。
 auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
 RefCountBits newbits;
 do {
 newbits = oldbits;
 // 4. 調(diào)用 InlineRefCountBits 的 incrementStrongExtraRefCount 方法
 // 對這個 uint64_t 進行一系列運算。
 bool fast = newbits.incrementStrongExtraRefCount(inc);
 // 無 weak、unowned 引用時一般不會進入。
 if (SWIFT_UNLIKELY(!fast)) {
  if (oldbits.isImmortal())
  return;
  return incrementSlow(oldbits, inc);
 }
 // 5. 通過 CAS 將運算后的 uint64_t 設(shè)置回去。
 } while (!refCounts.compare_exchange_weak(oldbits, newbits,
           std::memory_order_relaxed));
}

到這里就完成了一次 retain 操作。

SideTableRefCountBits

上面是不存在 weak、unowned 引用的情況,現(xiàn)在我們來看看增加一個 weak 引用會怎樣。

  1. 調(diào)用 SIL 接口 swift::swift_weakAssign(暫時省略這塊的邏輯,它屬于引用者的邏輯,我們現(xiàn)在先分析被引用者)
  2. 調(diào)用 RefCounts<InlineRefCountBits>::formWeakReference 增加一個弱引用:
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
 // 分配一個 Side Table。
 auto side = allocateSideTable(true);
 if (side)
 // 增加一個弱引用。
 return side->incrementWeak();
 else
 return nullptr;
}

重點來看一下 allocateSideTable 的實現(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 對象。
 HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());

 auto newbits = InlineRefCountBits(side);

 do {
 if (oldbits.hasSideTable()) {
  // 此時可能其他線程創(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);
 // 進行 CAS。
 } while (! refCounts.compare_exchange_weak(oldbits, newbits,
            std::memory_order_release,
            std::memory_order_relaxed));
 return side;
}

還記得 HeapObject 里的 RefCounts 實際上是 InlineRefCountBits 的一個 wrapper 嗎?上面構(gòu)造完 Side Table 以后,對象中的 InlineRefCountBits 就不是原來的引用計數(shù)了,而是一個指向 Side Table 的指針,然而由于它們實際都是 uint64_t,因此需要一個方法來區(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);
 }

其實還是最常見的方法,把指針地址無用的位替換成標(biāo)識位。

順便,看一下 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ù)會怎樣呢?來看下之前的 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);
 // ---> 這次進入這個分支。
 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 是一個 RefCounts<SideTableRefCountBits> 對象。
 refCounts.increment(inc);
}

到這里我們就需要引出 SideTableRefCountBits 了,它與前面的 InlineRefCountBits 很像,只不過又多了一個字段,看一下定義:

class SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
 uint32_t weakBits;

 // ...

};

小結(jié)一下

不知道上面的內(nèi)容大家看暈了沒有,反正我一開始分析的時候費了點時間。

上面我們講了兩種 RefCounts,一種是 inline 的,用在 HeapObject 中,它其實是一個 uint64_t,可以當(dāng)引用計數(shù)也可以當(dāng) Side Table 的指針。

Side Table 是一種類名為 HeapObjectSideTableEntry 的結(jié)構(gòu),里面也有 RefCounts 成員,是內(nèi)部是 SideTableRefCountBits,其實就是原來的 uint64_t 加上一個存儲弱引用數(shù)的 uint32_t。

WeakReference

上面說的都是被引用的對象所涉及的邏輯,而引用者這邊的邏輯就稍微簡單一些了,主要就是通過 WeakReference 這個類來實現(xiàn)的,比較簡單,我們簡單過一下就行。

Swift 中的 weak 變量經(jīng)過 silgen 之后都會變成 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");
 // 銷毀原來對象的弱引用。
 destroyOldNativeBits(oldBits);
}

弱引用的訪問就更簡單了:

HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) {
 auto side = bits.getNativeOrNull();
 return side ? side->tryRetain() : nullptr;
}

到這里大家發(fā)現(xiàn)一個問題沒有,被引用對象釋放了為什么還能直接訪問 Side Table?其實 Swift ABI 中 Side Table 的生命周期與對象是分離的,當(dāng)強引用計數(shù)為 0 時,只有 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)存全部被釋放,因為只要 weak 變量不被顯式置 nil,Side Table 就會存在。而 ABI 中也有可以提升的地方,那就是如果訪問弱引用變量時發(fā)現(xiàn)被引用對象已經(jīng)釋放,就將自己的弱引用銷毀掉,避免之后重復(fù)無意義的 CAS 操作。當(dāng)然 ABI 不做這個優(yōu)化,我們也可以在 Swift 代碼里做。:)

總結(jié)

以上就是 Swift 弱引用機制實現(xiàn)方式的一個簡單的分析,可見思路與 Objective-C runtime 還是很類似的,都采用與對象匹配的 Side Table 來維護引用計數(shù)。不同的地方就是 Objective-C 對象在內(nèi)存布局中沒有 Side Table 指針,而是通過一個全局的 StripedMap 來維護對象和 Side Table 之間的關(guān)系,效率沒有 Swift 這么高。另外 Objective-C runtime 在對象釋放時會將所有的 __weak 變量都 zero-out,而 Swift 并沒有。

總的來說,Swift 的實現(xiàn)方式會稍微簡單一些(雖然代碼更復(fù)雜,Swift 團隊追求更高的抽象)。第一次分析 Swift ABI,本文僅供參考,如果存在錯誤,歡迎大家勘正。感謝!

好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對我們的支持。

上一篇:深入講解Swift中的模式匹配

欄    目:Swift

下一篇:Swift仿選擇電影票的效果并實現(xiàn)無限/自動輪播的方法

本文標(biāo)題:Swift源碼解析之弱引用

本文地址:http://mengdiqiu.com.cn/a1/Swift/11935.html

網(wǎng)頁制作CMS教程網(wǎng)絡(luò)編程軟件編程腳本語言數(shù)據(jù)庫服務(wù)器

如果侵犯了您的權(quán)利,請與我們聯(lián)系,我們將在24小時內(nèi)進行處理、任何非本站因素導(dǎo)致的法律后果,本站均不負(fù)任何責(zé)任。

聯(lián)系QQ:835971066 | 郵箱:835971066#qq.com(#換成@)

Copyright © 2002-2020 腳本教程網(wǎng) 版權(quán)所有