本文在個人自建博客 http://blog.sunnyxx.com/2014/04/02/objc_dig_arc_dealloc/ 初始發表 ,cnblogs同步發表。直接複製的html,格式可能有點亂,見諒。html
此次探索源自於本身一直以來對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
時代,只剩下了下面的代碼:git
1 2 3 4 5 6 |
- (void)dealloc { // ... // // 非Objc對象內存的釋放,如CFRelease(...) // ... // } |
問題來了:github
[super dealloc]
,上層的析構去哪兒了?
llvm官方的ARC文檔中對ARC下的dealloc過程作了簡單說明,從中仍是能找出些有用的信息:objective-c
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.安全
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.app
因此,不用主調[super dealloc]
是由於自動調了,後面再說如何實現的;ARC下實例變量在根類NSObject析構時析構,下面就探究下。ide
經過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
這個東西里面沒跑了。
上面找到的名爲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借用了這個方法插入代碼實現了自動內存釋放的工做
最好的辦法仍是寫個測試代碼把這個隱藏的方法找出來,其實在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
方法,通過幾回試驗,發現:
依然在after new
斷點處,輸入lldb命令:
1 |
watchpoint set variable son->_name |
將name
的變量加入watchpoint,當這個變量被修改時會觸發trigger:
從中能夠看出,在這個時刻,_name
從0x00006b98變成了0x0,也就是nil,趕忙看下調用棧:
發現果真跟到了.cxx_destruct
方法,並且是在objc_storeStrong
的過程當中釋放
知道了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]
也必定是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]
方法。
.cxx_desctruct
方法自動釋放[super dealloc]
方法也由編譯器自動插入編譯器插入代碼
過程須要進一步瞭解,還不清楚其運做方式CodeGen
也值得深刻研究一下by sunnyxx, 原創文章,轉載請保留博客地址 blog.sunnyxx.com