iOS中的weak指標
ObjC runtime是如何實現weak指標的
-
用strong指標建立weak指標,系統會呼叫objc_initWeak()函式,初始化一個新的weak指標指向物件的地址,用 weak 指向的物件記憶體地址作為 key, weak指標的地址作為value放入一個 hash 表(weak table)中,如果不存在weak table則建立weak table
-
如果重新對weak指標賦值,則通過objc_storeWeak() 函式修改weak table
-
使用一個weak指標建立另一個weak指標時,會呼叫objc_copyWeak()
-
讀取weak 指標時,會通過objc_loadWeakRetained(&weak指標)獲取其指向的物件(retain一次),然後把它加到autoreleasepool中
並且!!!訪問多少次weak 指標就會呼叫這麼多次objc_loadWeakRetained和新增這麼多次autoreleasepool,所以最好在block中拿到weak指標後,用一個__strong指標指向它,避免多餘的操作,並且最好直接判斷一下是否為nil,是的話返回不做處理
-
weak指標離開作用域時,會呼叫objc_destroyWeak() 函式把該指標從weak table中移除
-
當此物件的引用計數為0的時候會 dealloc, dealloc的最後會呼叫object_dispose()函式,觸發objc_clear_deallocating()函式在 weak 表中獲取物件的地址對應的weak指標陣列,從而設定為 nil,並且把相關的weak指標從weak table中移除
ps:
weak在物件建立之前和銷燬之後都是預設nil
swift中的不橋接ObjC的話,weak實現方式會稍微有點不一樣:
這裡大概講一下swift3之後的實現:
弱指標基本等同於普通的指標。
弱引用指向物件例項的side table地址
當一個弱引用物件的deinit執行後,物件並沒有被釋放,且弱引用指標也沒有被賦nil。
當弱引用執行完deinit後,訪問弱引用物件,則物件指標才會被賦nil,且目標物件被釋放。
弱引用物件對於每一個弱引用會包含一個引用計數(unowned計數和strong計數為同一個),且與強引用計數分開統計(但是統一管理的,只有當兩者都為0才會被釋放)。
這是因為Swift的計數方式預設為InlineRefCounts,當物件只包含strong或unowned引用時,使用InlineRefCounts進行計數管理,當物件擁有了weak引用,InlineRefCounts會變為SideTableRefCounts
也就是說,swift物件的析構和物件的釋放不一定是同時的,當Swift物件的strong引用計數變為0但是weak計數大於0時,物件會被析構但是不會被釋放記憶體
底層實現
系統在嘗試讀取weak指標時,會通過HeapObject *swift::swift_weakTakeStrong(WeakReference *ref)函式操作,該函式最終會來到HeapObject *nativeTakeStrongFromBits(WeakReferenceBits bits) 函式
在這個函式中,會通過getNativeOrNull方法從物件的side table中查詢物件的計數,當沒有strong引用時,說明該物件已經處於DEINITING狀態,函式返回nullptr,接著外層函式會賦空weak引用,weak計數減一,釋放剩餘的物件記憶體,並返回nil
否則將呼叫weak的引用自減計數函式decrementWeak(),再呼叫refCounts的decrementWeakShouldCleanUp()函式進行位數操作,當weak、strong、unowned計數都變為0時函式會返回true,這時候系統會清空weak指標對應的side table,最後回到nativeTakeStrongFromBits呼叫tryRetain函式來嘗試獲取物件
完整實現大致如下:
HeapObject *nativeTakeStrongFromBits(WeakReferenceBits bits) { auto side = bits.getNativeOrNull(); if (side) { side->decrementWeak(); return side->tryRetain(); } else { return nullptr; } } void decrementWeak() { bool cleanup = refCounts.decrementWeakShouldCleanUp(); if (!cleanup) return; assert(refCounts.getUnownedCount() == 0); delete this; }
同時,新增weak物件會使物件的引用計數管理會從InlineRefCounts替換為SideTableRefCounts,這也會帶來一定的開銷,對於有效能要求的場景swift提供了unowned,unowned的行為跟strong是一樣的,但不會使計數增加,代價是物件被釋放了的話,訪問unowned指標就是未定義的行為(相當於ObjC中的訪問野指標)