前言
這次探索源自於自己一直以來對ARC
的一個疑問,在MRC
時代,經常寫下面的程式碼:
1 2 3 4 5 6 7 8 9 |
- (void)dealloc { self.array = nil; self.string = nil; // ... // // 非Objc物件記憶體的釋放,如CFRelease(...) // ... // [super dealloc]; } |
物件析構時將內部其他物件release
掉,申請的非Objc物件的記憶體當然也一併處理掉,最後呼叫super
,繼續將父類物件做析構。而現如今到了ARC
時代,只剩下了下面的程式碼:
1 2 3 4 5 6 |
- (void)dealloc { // ... // // 非Objc物件記憶體的釋放,如CFRelease(...) // ... // } |
問題來了:
- 這個物件例項變數(Ivars)的釋放去哪兒了?
- 沒有顯示的呼叫
[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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void *objc_destructInstance(id obj) { if (obj) { Class isa_gen = _object_getClass(obj); class_t *isa = newcls(isa_gen); // Read all of the flags at once for performance. bool cxx = hasCxxStructors(isa); bool assoc = !UseGC && _class_instancesHaveAssociatedObjects(isa_gen); // This order is important. if (cxx) object_cxxDestruct(obj); if (assoc) _object_remove_assocations(obj); if (!UseGC) objc_clear_deallocating(obj); } return obj; } |
簡單明確的幹了三件事:
- 執行一個叫
object_cxxDestruct
的東西幹了點什麼事 - 執行
_object_remove_assocations
去除和這個物件assocate的物件(常用於category中新增帶變數的屬性,這也是為什麼ARC下沒必要remove一遍的原因) - 執行
objc_clear_deallocating
,清空引用計數表並清除弱引用表,將所有weak
引用指nil(這也就是weak變數能安全置空的所在)
所以,所探尋的ARC自動釋放例項變數的地方就在cxxDestruct
這個東西里面沒跑了。
探尋隱藏的.cxx_destruct
上面找到的名為object_cxxDestruct
的方法最終成為下面的呼叫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
static void object_cxxDestructFromClass(id obj, Class cls) { void (*dtor)(id); // Call cls's dtor first, then superclasses's dtors. for ( ; cls != NULL; cls = _class_getSuperclass(cls)) { if (!_class_hasCxxStructors(cls)) return; dtor = (void(*)(id)) lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct); if (dtor != (void(*)(id))_objc_msgForward_internal) { if (PrintCxxCtors) { _objc_inform("CXX: calling C++ destructors for class %s", _class_getName(cls)); } (*dtor)(obj); } } } |
程式碼也不難理解,沿著繼承鏈逐層向上搜尋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中執行已經沒什麼隱藏可言了,簡單的類結構如下:
1 2 3 4 5 6 7 |
@interface Father : NSObject @property (nonatomic, copy) NSString *name; @end @interface Son : Father @property (nonatomic, copy) NSArray *toys; @end |
只有兩個簡單的屬性,找個地方寫簡單的測試程式碼:
1 2 3 4 5 6 7 8 9 |
// start { // before new Son *son = [Son new]; son.name = @"sark"; son.toys = @[@"sunny", @"xx"]; // after new } // gone |
主要目的是為了讓這個物件走dealloc方法,新建的son物件過了大括號作用域就會釋放了,所以在after new
這行son物件初始化完成,在gone
這行son物件被dealloc
個人一直喜歡使用NSObject+DLIntrospection這個擴充套件作為除錯工具,可以輕鬆打出一個類的方法,變數等等。
將這個擴充套件引入工程內,在after new
處設定一個斷點,run,trigger後使用lldb命令用這個擴充套件輸出Son類所有的方法名:
發現了這個.cxx_destruct
方法,經過幾次試驗,發現:
- 只有在ARC下這個方法才會出現(試驗程式碼的情況下)
- 只有當前類擁有例項變數時(不論是不是用property)這個方法才會出現,且父類的例項變數不會導致子類擁有這個方法
- 出現這個方法和變數是否被賦值,賦值成什麼沒有關係
使用watchpoint定位記憶體釋放時刻
依然在after new
斷點處,輸入lldb命令:
1 |
watchpoint set variable son->_name |
將name
的變數加入watchpoint,當這個變數被修改時會觸發trigger:
從中可以看出,在這個時刻,_name
從0x00006b98變成了0x0,也就是nil,趕緊看下呼叫棧:
發現果然跟到了.cxx_destruct
方法,而且是在objc_storeStrong
的過程中釋放
刨根問底.cxx_destruct
知道了ARC下物件例項變數的釋放過程在.cxx_destruct
內完成,但這個函式內部發生了什麼,是如何呼叫objc_storeStrong
釋放變數的呢?
從上面的探究中知道,.cxx_destruct
是編譯器生成的程式碼,那它很可能在clang前端編譯時完成,這讓我聯想到clang的Code Generation
,因為之前曾經使用clang -rewrite-objc xxx.m
時檢視過官方文件留下了些印象,於是google:
1 |
.cxx_destruct site:clang.llvm.org |
結果發現clang的doxygen
文件中CodeGenModule
模組正是這部分的實現程式碼,cxx相關的程式碼生成部分原始碼在
http://clang.llvm.org/doxygen/CodeGenModule_8cpp-source.html
位於1827行,刪減掉離題部分如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
/// EmitObjCIvarInitializations - Emit information for ivar initialization /// for an implementation. void CodeGenModule::EmitObjCIvarInitializations(ObjCImplementationDecl *D) { DeclContext* DC = const_cast<DeclContext*>(dyn_cast<DeclContext>(D)); assert(DC && "EmitObjCIvarInitializations - null DeclContext"); IdentifierInfo *II = &getContext().Idents.get(".cxx_destruct"); Selector cxxSelector = getContext().Selectors.getSelector(0, &II); ObjCMethodDecl *DTORMethod = ObjCMethodDecl::Create(getContext(), D->getLocation(), D->getLocation(), cxxSelector, getContext().VoidTy, 0, DC, true, false, true, ObjCMethodDecl::Required); D->addInstanceMethod(DTORMethod); CodeGenFunction(*this).GenerateObjCCtorDtorMethod(D, DTORMethod, false); } |
這個函式大概作用是:獲取.cxx_destruct
的selector,建立Method,並加入到這個Class的方法列表中,最後一行的呼叫才是真的建立這個方法的實現。這個方法位於
http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html
1354行,包含了構造和析構的cxx方法,繼續跟隨.cxx_destruct
,最終呼叫emitCXXDestructMethod
函式,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
static void emitCXXDestructMethod(CodeGenFunction &CGF, ObjCImplementationDecl *impl) { CodeGenFunction::RunCleanupsScope scope(CGF); llvm::Value *self = CGF.LoadObjCSelf(); const ObjCInterfaceDecl *iface = impl->getClassInterface(); for (const ObjCIvarDecl *ivar = iface->all_declared_ivar_begin(); ivar; ivar = ivar->getNextIvar()) { QualType type = ivar->getType(); // Check whether the ivar is a destructible type. QualType::DestructionKind dtorKind = type.isDestructedType(); if (!dtorKind) continue; CodeGenFunction::Destroyer *destroyer = 0; // Use a call to objc_storeStrong to destroy strong ivars, for the // general benefit of the tools. if (dtorKind == QualType::DK_objc_strong_lifetime) { destroyer = destroyARCStrongWithStore; // Otherwise use the default for the destruction kind. } else { destroyer = CGF.getDestroyer(dtorKind); } CleanupKind cleanupKind = CGF.getCleanupKind(dtorKind); CGF.EHStack.pushCleanup<DestroyIvar>(cleanupKind, self, ivar, destroyer, cleanupKind & EHCleanup); } assert(scope.requiresCleanups() && "nothing to do in .cxx_destruct?"); } |
分析這段程式碼以及其中呼叫後發現:它遍歷當前物件所有的例項變數(Ivars),呼叫objc_storeStrong
,從clang
的ARC文件上可以找到objc_storeStrong
的示意程式碼實現如下:
1 2 3 4 5 6 7 |
id objc_storeStrong(id *object, id value) { value = [value retain]; id oldValue = *object; *object = value; [oldValue release]; return value; } |
在.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
方法中:
1 2 |
if (ident->isStr("dealloc")) EHStack.pushCleanup<FinishARCDealloc>(getARCCleanupKind()); |
上面程式碼可以得知在呼叫dealloc
方法時被插入了程式碼,由FinishARCDealloc
結構定義:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
struct FinishARCDealloc : EHScopeStack::Cleanup { void Emit(CodeGenFunction &CGF, Flags flags) override { const ObjCMethodDecl *method = cast<ObjCMethodDecl>(CGF.CurCodeDecl); const ObjCImplDecl *impl = cast<ObjCImplDecl>(method->getDeclContext()); const ObjCInterfaceDecl *iface = impl->getClassInterface(); if (!iface->getSuperClass()) return; bool isCategory = isa<ObjCCategoryImplDecl>(impl); // Call [super dealloc] if we have a superclass. llvm::Value *self = CGF.LoadObjCSelf(); CallArgList args; CGF.CGM.getObjCRuntime().GenerateMessageSendSuper(CGF, ReturnValueSlot(), CGF.getContext().VoidTy, method->getSelector(), iface, isCategory, self, /*is class msg*/ false, args, method); } }; |
上面程式碼基本上就是向父類轉發dealloc
的呼叫,實現了自動呼叫[super dealloc]
方法。
總結
- ARC下物件的成員變數於編譯器插入的
.cxx_desctruct
方法自動釋放 - ARC下
[super dealloc]
方法也由編譯器自動插入 - 所謂
編譯器插入程式碼
過程需要進一步瞭解,還不清楚其運作方式 - clang的
CodeGen
也值得深入研究一下