ARC下dealloc過程及.cxx_destruct的探究

本文在個人自建博客 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

  1. 這個對象實例變量(Ivars)的釋放去哪兒了?
  2. 沒有顯示的調用[super dealloc],上層的析構去哪兒了?

 

ARC文檔中對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.安全

  • 大概意思是: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.app

  • 理解:ARC下對象的實例變量在根類[NSObject dealloc]中釋放(一般root class都是NSObject),變量釋放順序各類不肯定(一個類內的不肯定,子類和父類間也不肯定,也就是說不用care釋放順序)

因此,不用主調[super dealloc]是由於自動調了,後面再說如何實現的;ARC下實例變量在根類NSObject析構時析構,下面就探究下。ide


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;
}

簡單明確的幹了三件事:工具

  1. 執行一個叫object_cxxDestruct的東西幹了點什麼事
  2. 執行_object_remove_assocations去除和這個對象assocate的對象(經常使用於category中添加帶變量的屬性,這也是爲何ARC下不必remove一遍的緣由)
  3. 執行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方法,通過幾回試驗,發現:

  1. 只有在ARC下這個方法纔會出現(試驗代碼的狀況下)
  2. 只有當前類擁有實例變量時(不管是不是用property)這個方法纔會出現,且父類的實例變量不會致使子類擁有這個方法
  3. 出現這個方法和變量是否被賦值,賦值成什麼沒有關係

使用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也值得深刻研究一下

References:


by sunnyxx, 原創文章,轉載請保留博客地址 blog.sunnyxx.com

相關文章
相關標籤/搜索