ARC下dealloc過程及.cxx_destruct的探究

發表於2014-04-14

前言

這次探索源自於自己一直以來對ARC的一個疑問,在MRC時代,經常寫下面的程式碼:

物件析構時將內部其他物件release掉,申請的非Objc物件的記憶體當然也一併處理掉,最後呼叫super,繼續將父類物件做析構。而現如今到了ARC時代,只剩下了下面的程式碼:

問題來了:

  1. 這個物件例項變數(Ivars)的釋放去哪兒了?
  2. 沒有顯示的呼叫[super dealloc],上層的析構去哪兒了?

ARC文件中對dealloc過程的解釋

llvm官方的ARC文件中對ARC下的dealloc過程做了簡單說明,從中還是能找出些有用的資訊:

A class may provide a method definition for an instance method named dealloc. This method will be called after the final release of the object but before it is deallocated or any of its instance variables are destroyed. The superclass’s implementation of dealloc will be called automatically when the method returns.

  • 大概意思是:dealloc方法在最後一次release後被呼叫,但此時例項變數(Ivars)並未釋放,父類的dealloc的方法將在子類dealloc方法返回後自動呼叫

The instance variables for an ARC-compiled class will be destroyed at some point after control enters the dealloc method for the root class of the class. The ordering of the destruction of instance variables is unspecified, both within a single class and between subclasses and superclasses.

  • 理解:ARC下物件的例項變數在根類[NSObject dealloc]中釋放(通常root class都是NSObject),變數釋放順序各種不確定(一個類內的不確定,子類和父類間也不確定,也就是說不用care釋放順序)

所以,不用主調[super dealloc]是因為自動調了,後面再說如何實現的;ARC下例項變數在根類NSObject析構時析構,下面就探究下。

NSObject的析構過程

通過apple的runtime原始碼,不難發現NSObject執行dealloc時呼叫_objc_rootDealloc繼而呼叫object_dispose隨後呼叫objc_destructInstance方法,前幾步都是條件判斷和簡單的跳轉,最後的這個函式如下:

簡單明確的幹了三件事:

  1. 執行一個叫object_cxxDestruct的東西幹了點什麼事
  2. 執行_object_remove_assocations去除和這個物件assocate的物件(常用於category中新增帶變數的屬性,這也是為什麼ARC下沒必要remove一遍的原因)
  3. 執行objc_clear_deallocating,清空引用計數表並清除弱引用表,將所有weak引用指nil(這也就是weak變數能安全置空的所在)

所以,所探尋的ARC自動釋放例項變數的地方就在cxxDestruct這個東西里面沒跑了。

探尋隱藏的.cxx_destruct

上面找到的名為object_cxxDestruct的方法最終成為下面的呼叫:

程式碼也不難理解,沿著繼承鏈逐層向上搜尋SEL_cxx_destruct這個selector,找到函式實現(void (*)(id)(函式指標)並執行。
搜尋這個selector的宣告,發現是名為.cxx_destruct的方法,以點開頭的名字,我想和unix的檔案一樣,是有隱藏屬性的

這篇文章中:

ARC actually creates a -.cxx_destruct method to handle freeing instance variables. This method was originally created for calling C++ destructors automatically when an object was destroyed.

和《Effective Objective-C 2.0》中提到的:

When the compiler saw that an object contained C++ objects, it would generate a method called .cxx_destruct. ARC piggybacks on this method and emits the required cleanup code within it.

可以瞭解到,.cxx_destruct方法原本是為了C++物件析構的,ARC借用了這個方法插入程式碼實現了自動記憶體釋放的工作

通過實驗找出.cxx_destruct

最好的辦法還是寫個測試程式碼把這個隱藏的方法找出來,其實在runtime中執行已經沒什麼隱藏可言了,簡單的類結構如下:

只有兩個簡單的屬性,找個地方寫簡單的測試程式碼:

主要目的是為了讓這個物件走dealloc方法,新建的son物件過了大括號作用域就會釋放了,所以在after new這行son物件初始化完成,在gone這行son物件被dealloc

個人一直喜歡使用NSObject+DLIntrospection這個擴充套件作為除錯工具,可以輕鬆打出一個類的方法,變數等等。

將這個擴充套件引入工程內,在after new處設定一個斷點,run,trigger後使用lldb命令用這個擴充套件輸出Son類所有的方法名:

rehe51530583gw1ef27srhw7lj208b05ujrq

發現了這個.cxx_destruct方法,經過幾次試驗,發現:

  1. 只有在ARC下這個方法才會出現(試驗程式碼的情況下)
  2. 只有當前類擁有例項變數時(不論是不是用property)這個方法才會出現,且父類的例項變數不會導致子類擁有這個方法
  3. 出現這個方法和變數是否被賦值,賦值成什麼沒有關係

使用watchpoint定位記憶體釋放時刻

依然在after new斷點處,輸入lldb命令:


name的變數加入watchpoint,當這個變數被修改時會觸發trigger:

regreg51530583gw1ef28rn41lcj20fs03aq3b

從中可以看出,在這個時刻,_name從0x00006b98變成了0x0,也就是nil,趕緊看下呼叫棧:

erhrth51530583gw1ef2911o40zj20a605yweu

發現果然跟到了.cxx_destruct方法,而且是在objc_storeStrong的過程中釋放

刨根問底.cxx_destruct

知道了ARC下物件例項變數的釋放過程在.cxx_destruct內完成,但這個函式內部發生了什麼,是如何呼叫objc_storeStrong釋放變數的呢?
從上面的探究中知道,.cxx_destruct是編譯器生成的程式碼,那它很可能在clang前端編譯時完成,這讓我聯想到clang的Code Generation,因為之前曾經使用clang -rewrite-objc xxx.m時檢視過官方文件留下了些印象,於是google:

結果發現clang的doxygen文件中CodeGenModule模組正是這部分的實現程式碼,cxx相關的程式碼生成部分原始碼在
http://clang.llvm.org/doxygen/CodeGenModule_8cpp-source.html
位於1827行,刪減掉離題部分如下:

這個函式大概作用是:獲取.cxx_destruct的selector,建立Method,並加入到這個Class的方法列表中,最後一行的呼叫才是真的建立這個方法的實現。這個方法位於
http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html
1354行,包含了構造和析構的cxx方法,繼續跟隨.cxx_destruct,最終呼叫emitCXXDestructMethod函式,程式碼如下:

分析這段程式碼以及其中呼叫後發現:它遍歷當前物件所有的例項變數(Ivars),呼叫objc_storeStrong,從clang的ARC文件上可以找到objc_storeStrong的示意程式碼實現如下:

.cxx_destruct進行形如objc_storeStrong(&ivar, null)的呼叫後,這個例項變數就被release和設定成nil
注:真實的實現可以參考 http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html 2078行

自動呼叫[super dealloc]的實現

按照上面的思路,自動呼叫[super dealloc]也一定是CodeGen乾的工作了 位於 http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html 492行
StartObjCMethod方法中:

上面程式碼可以得知在呼叫dealloc方法時被插入了程式碼,由FinishARCDealloc結構定義:

上面程式碼基本上就是向父類轉發dealloc的呼叫,實現了自動呼叫[super dealloc]方法。

總結

  • ARC下物件的成員變數於編譯器插入的.cxx_desctruct方法自動釋放
  • ARC下[super dealloc]方法也由編譯器自動插入
  • 所謂編譯器插入程式碼過程需要進一步瞭解,還不清楚其運作方式
  • clang的CodeGen也值得深入研究一下

相關文章