Swift內(nèi)存管理初探

HeapObject

在Swift中,一個(gè)Class對象實(shí)際上就是一個(gè)HeapObject結(jié)構(gòu)體指針放钦。那么它的內(nèi)存布局是怎樣的呢? 首先我們先來看一下 HeapObject 的結(jié)構(gòu):

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)
  { }

};

從中我們可以看到:
第一個(gè)字段是一個(gè) Metadata 對象操禀,該對象有著與OC中 isa_t 類似的作用颓屑,用來描述對象類型的(等價(jià)于 type(of:) 取得的結(jié)果)! 但本文的重點(diǎn)不是在這耿焊,我們繼續(xù)往下看!

這才是本文的主角哦: SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS (這是一個(gè)宏定義) !

我們點(diǎn)開這個(gè)宏來看看這是個(gè)啥?

RefCounts<InlineRefCountBits> refCounts;

這就是Swift中引用計(jì)數(shù) refCounts了,引用計(jì)數(shù)器腋、弱引用纫塌、unowned 引用都與它有關(guān)!

繼續(xù)深挖定義refCounts的這個(gè)類 InlineRefCounts

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

InlineRefCounts: 是用在常規(guī)對象以及無主引用中
SideTableRefCounts: 則是用在weak引用對象中

InlineRefCounts 被起了個(gè)別名為 RefCounts讲弄,我們看繼續(xù)看下 RefCounts類結(jié)構(gòu)

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

這里省略了所有的方法和類型定義! 注意: 這是模版類避除,那么就要注意傳進(jìn)來的模版參數(shù)

首先我們來看看 InlineRefCountBits (常規(guī)對象)

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! 位圖如下:


占位@2x.png

接下來我們在看看 SideTableRefCounts (weak引用)

首先我們先來寫一段代碼群井,看看能從匯編中發(fā)現(xiàn)什么?

class People {
    var age = 10
}

let p = People()
weak var p2 = p
匯編@2x.png

咦,這個(gè)swift_weakInit是什么? 它又做了什么? 我們繼續(xù)往里看

WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
  ref->nativeInit(value);
  return ref;
}

從上述代碼可以看出該對象為WeakReference類! 那么 weak 變量指蚁, 編譯器則會在聲明的過程中就定義了WeakReference對象自晰,該對象主要目的就是用來管理當(dāng)前的弱引用對象!
該對象調(diào)用了其中的 nativeInit(value) 方法酬荞;傳遞了當(dāng)前的HeapObject對象

繼續(xù)看 nativeInit 具體操作

void nativeInit(HeapObject *object) {
    auto side = object ? object->refCounts.formWeakReference() : nullptr;
    nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}

如果當(dāng)前對象不為空則調(diào)用object->refCounts.formWeakReference()方法、我們查看下 formWeakReference方法的實(shí)現(xiàn)

template <> HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}

創(chuàng)建SideTable枪向、如果創(chuàng)建成功秘蛔,則執(zhí)行 incrementWeak();
查看allocateSideTable創(chuàng)建過程

template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
  auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
  // Preflight failures before allocating a new side table.
  if (oldbits.hasSideTable()) {
    // Already have a side table. Return it.
    return oldbits.getSideTable();
  } 
  else if (failIfDeiniting && oldbits.getIsDeiniting()) {
    // Already past the start of deinit. Do nothing.
    return nullptr;
  }
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  auto newbits = InlineRefCountBits(side);
  ....
  return side;
}
  • 首先拿到原有的引用計(jì)數(shù) refCounts.load(SWIFT_MEMORY_ORDER_CONSUME)
  • 然后通過 getHeapObject() 創(chuàng)建一個(gè) HeapObjectSideTableEntry 實(shí)例對象 side
  • 再把創(chuàng)建出來的 side對象地址給 InlineRefCountBits 深员、而InlineRefCountBits 就是我們分析強(qiáng)引用計(jì)數(shù)的 RefCountBitsT!

順便一起來看一下 HeapObjectSideTableEntry 的結(jié)構(gòu):

class HeapObjectSideTableEntry {
  std::atomic<HeapObject*> object;
  SideTableRefCounts refCounts;
  public:
  HeapObjectSideTableEntry(HeapObject *newObject)
    : object(newObject), refCounts()
  { }
  ......
}

在HeapObjectSideTableEntry中保留了原對象的Metadata和refCound!
還有bits(強(qiáng)引用蛙埂、無主引用的信息) 以及新增的 weakBits(弱引用信息)

小結(jié)

swift對象都是以HeapObject為模板創(chuàng)建!
RefCounts:
第一種是 InlineRefCounts 绣的,用在 常規(guī)對象(無主) 中,它其實(shí)是一個(gè) uint64_t!
第二種是SideTableRefCounts, 用在weak對象中芭概,此時(shí)它是一個(gè)指向 SideTable(HeapObjectSideTableEntry) 的指針盼理。

注意點(diǎn): OC與Swift區(qū)別

OC:
弱引用計(jì)數(shù)是存放在全局維護(hù)的散列表中俄删,isa中會記錄是否使用了散列表。
在引用計(jì)數(shù)為0時(shí)臊诊,自動觸發(fā)dealloc斜脂,會檢查并清空當(dāng)前對象的散列表計(jì)數(shù)。

Swift
弱引用計(jì)數(shù)也是存放在散列表中玷或,這個(gè)散列表是局部的! 每個(gè)weak對象都有自己的散列表!
即便一個(gè)對象在使用了弱引用后南窗,也不能保證相關(guān)內(nèi)存全部被釋放! 因?yàn)镾ideTable 的生命周期與對象是分離的奠骄,當(dāng)強(qiáng)引用計(jì)數(shù)為 0 時(shí)部服,只有 HeapObject 被釋放了,但不會觸發(fā)SideTable的清空鹅髓。而是在下次訪問時(shí)發(fā)現(xiàn)當(dāng)前對象為nil時(shí),才會清空SideTable

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骗奖,一起剝皮案震驚了整個(gè)濱河市重归,隨后出現(xiàn)的幾起案子厦凤,更是在濱河造成了極大的恐慌,老刑警劉巖椎木,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件香椎,死亡現(xiàn)場離奇詭異禽篱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)玛界,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門慎框,熙熙樓的掌柜王于貴愁眉苦臉地迎上來后添,“玉大人,你說我怎么就攤上這事馅精。” “怎么了阻问?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵称近,是天一觀的道長哮塞。 經(jīng)常有香客問我,道長衡未,這世上最難降的妖魔是什么家凯? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮送粱,結(jié)果婚禮上掂之,老公的妹妹穿的比我還像新娘。我一直安慰自己动雹,他們只是感情好跟压,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布震蒋。 她就那樣靜靜地躺著,像睡著了一般翔横。 火紅的嫁衣襯著肌膚如雪梗搅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天荡短,我揣著相機(jī)與錄音掘托,去河邊找鬼籍嘹。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泪掀,可吹牛的內(nèi)容都是我干的颂碘。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼塔拳,長吁一口氣:“原來是場噩夢啊……” “哼峡竣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起孕荠,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤稚伍,失蹤者是張志新(化名)和其女友劉穎戚宦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體垦搬,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡猴贰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年河狐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瑟捣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迈套。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡桑李,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出贵白,到底是詐尸還是另有隱情崩泡,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布圈浇,位于F島的核電站磷蜀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏褐隆。R本人自食惡果不足惜剖踊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一德澈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梆造,春花似錦、人聲如沸屡穗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽础废。三九已至,卻和暖如春色迂,著一層夾襖步出監(jiān)牢的瞬間手销,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工诈悍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侥钳。 一個(gè)月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓舷夺,卻偏偏與公主長得像,于是被迫代替她去往敵國和親给猾。 傳聞我的和親對象是個(gè)殘疾皇子颂跨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容