ARC下dealloc過程及.cxx_destruct的探究

來源:http://blog.sunnyxx.com/2014/04/02/objc_dig_arc_dealloc/

我是前言

這次探索源自于自己一直以來對ARC的一個(gè)疑問,在MRC時(shí)代征冷,經(jīng)常寫下面的代碼:

- (void)dealloc {

self.array =nil;

self.string =nil;

// ... //

// 非Objc對象內(nèi)存的釋放菩颖,如CFRelease(...)

// ... //

[superdealloc];

}

對象析構(gòu)時(shí)將內(nèi)部其他對象release掉,申請的非Objc對象的內(nèi)存當(dāng)然也一并處理掉晌缘,最后調(diào)用super,繼續(xù)將父類對象做析構(gòu)痢站。而現(xiàn)如今到了ARC時(shí)代磷箕,只剩下了下面的代碼:

- (void)dealloc

{

// ... //

// 非Objc對象內(nèi)存的釋放,如CFRelease(...)

// ... //

}

問題來了:

這個(gè)對象實(shí)例變量(Ivars)的釋放去哪兒了阵难?

沒有顯示的調(diào)用[super dealloc]岳枷,上層的析構(gòu)去哪兒了?

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后被調(diào)用空繁,但此時(shí)實(shí)例變量(Ivars)并未釋放,父類的dealloc的方法將在子類dealloc方法返回后自動(dòng)調(diào)用

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下對象的實(shí)例變量在根類[NSObject dealloc]中釋放(通常root class都是NSObject)朱庆,變量釋放順序各種不確定(一個(gè)類內(nèi)的不確定盛泡,子類和父類間也不確定,也就是說不用care釋放順序)

所以娱颊,不用主調(diào)[super dealloc]是因?yàn)樽詣?dòng)調(diào)了傲诵,后面再說如何實(shí)現(xiàn)的;ARC下實(shí)例變量在根類NSObject析構(gòu)時(shí)析構(gòu)箱硕,下面就探究下拴竹。

NSObject的析構(gòu)過程

通過apple的runtime源碼,不難發(fā)現(xiàn)NSObject執(zhí)行dealloc時(shí)調(diào)用_objc_rootDealloc繼而調(diào)用object_dispose隨后調(diào)用objc_destructInstance方法剧罩,前幾步都是條件判斷和簡單的跳轉(zhuǎn)栓拜,最后的這個(gè)函數(shù)如下:

void*objc_destructInstance(idobj)

{

if(obj) {

Class isa_gen = _object_getClass(obj);

class_t *isa = newcls(isa_gen);

// Read all of the flags at once for performance.

boolcxx = hasCxxStructors(isa);

boolassoc = !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);

}

returnobj;

}

簡單明確的干了三件事:

執(zhí)行一個(gè)叫object_cxxDestruct的東西干了點(diǎn)什么事

執(zhí)行_object_remove_assocations去除和這個(gè)對象assocate的對象(常用于category中添加帶變量的屬性,這也是為什么ARC下沒必要remove一遍的原因(Edit: 在ARC或MRC下都不需要remove,感謝@sagles的基情提示)

執(zhí)行objc_clear_deallocating菱属,清空引用計(jì)數(shù)表并清除弱引用表钳榨,將所有weak引用指nil(這也就是weak變量能安全置空的所在)

所以,所探尋的ARC自動(dòng)釋放實(shí)例變量的地方就在cxxDestruct這個(gè)東西里面沒跑了纽门。

探尋隱藏的.cxx_destruct

上面找到的名為object_cxxDestruct的方法最終成為下面的調(diào)用:

staticvoidobject_cxxDestructFromClass(idobj, 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這個(gè)selector,找到函數(shù)實(shí)現(xiàn)(void (*)(id)(函數(shù)指針)并執(zhí)行赏陵。

搜索這個(gè)selector的聲明饼齿,發(fā)現(xiàn)是名為.cxx_destruct的方法,以點(diǎn)開頭的名字蝙搔,我想和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++對象析構(gòu)的吃型,ARC借用了這個(gè)方法插入代碼實(shí)現(xiàn)了自動(dòng)內(nèi)存釋放的工作

通過實(shí)驗(yàn)找出.cxx_destruct

最好的辦法還是寫個(gè)測試代碼把這個(gè)隱藏的方法找出來证鸥,其實(shí)在runtime中運(yùn)行已經(jīng)沒什么隱藏可言了,簡單的類結(jié)構(gòu)如下:

@interfaceFather:NSObject

@property(nonatomic,copy)NSString*name;

@end

@interfaceSon:Father

@property(nonatomic,copy)NSArray*toys;

@end

只有兩個(gè)簡單的屬性勤晚,找個(gè)地方寫簡單的測試代碼:

// start

{

// before new

Son *son = [Son new];

son.name =@"sark";

son.toys = @[@"sunny",@"xx"];

// after new

}

// gone

主要目的是為了讓這個(gè)對象走dealloc方法枉层,新建的son對象過了大括號作用域就會(huì)釋放了,所以在after new這行son對象初始化完成赐写,在gone這行son對象被dealloc

個(gè)人一直喜歡使用NSObject+DLIntrospection這個(gè)擴(kuò)展作為調(diào)試工具鸟蜡,可以輕松打出一個(gè)類的方法,變量等等挺邀。

將這個(gè)擴(kuò)展引入工程內(nèi)揉忘,在after new處設(shè)置一個(gè)斷點(diǎn),run端铛,trigger后使用lldb命令用這個(gè)擴(kuò)展輸出Son類所有的方法名:

發(fā)現(xiàn)了這個(gè).cxx_destruct方法泣矛,經(jīng)過幾次試驗(yàn),發(fā)現(xiàn):

只有在ARC下這個(gè)方法才會(huì)出現(xiàn)(試驗(yàn)代碼的情況下)

只有當(dāng)前類擁有實(shí)例變量時(shí)(不論是不是用property)這個(gè)方法才會(huì)出現(xiàn)沦补,且父類的實(shí)例變量不會(huì)導(dǎo)致子類擁有這個(gè)方法

出現(xiàn)這個(gè)方法和變量是否被賦值乳蓄,賦值成什么沒有關(guān)系

使用watchpoint定位內(nèi)存釋放時(shí)刻

依然在after new斷點(diǎn)處咪橙,輸入lldb命令:

watchpoint set variable son->_name

將name的變量加入watchpoint夕膀,當(dāng)這個(gè)變量被修改時(shí)會(huì)觸發(fā)trigger:

從中可以看出,在這個(gè)時(shí)刻美侦,_name從0x00006b98變成了0x0产舞,也就是nil,趕緊看下調(diào)用棧:

發(fā)現(xiàn)果然跟到了.cxx_destruct方法菠剩,而且是在objc_storeStrong的過程中釋放

刨根問底.cxx_destruct

知道了ARC下對象實(shí)例變量的釋放過程在.cxx_destruct內(nèi)完成易猫,但這個(gè)函數(shù)內(nèi)部發(fā)生了什么,是如何調(diào)用objc_storeStrong釋放變量的呢具壮?

從上面的探究中知道准颓,.cxx_destruct是編譯器生成的代碼哈蝇,那它很可能在clang前端編譯時(shí)完成,這讓我聯(lián)想到clang的Code Generation攘已,因?yàn)橹霸?jīng)使用clang -rewrite-objc xxx.m時(shí)查看過官方文檔留下了些印象炮赦,于是google:

.cxx_destruct site:clang.llvm.org

結(jié)果發(fā)現(xiàn)clang的doxygen文檔中CodeGenModule模塊正是這部分的實(shí)現(xiàn)代碼,cxx相關(guān)的代碼生成部分源碼在

http://clang.llvm.org/doxygen/CodeGenModule_8cpp-source.html

位于1827行样勃,刪減掉離題部分如下:

/// EmitObjCIvarInitializations - Emit information for ivar initialization

/// for an implementation.

voidCodeGenModule::EmitObjCIvarInitializations(ObjCImplementationDecl *D)

{

DeclContext* DC =const_cast(dyn_cast(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);

}

這個(gè)函數(shù)大概作用是:獲取.cxx_destruct的selector吠勘,創(chuàng)建Method,并加入到這個(gè)Class的方法列表中峡眶,最后一行的調(diào)用才是真的創(chuàng)建這個(gè)方法的實(shí)現(xiàn)剧防。這個(gè)方法位于

http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html

1354行,包含了構(gòu)造和析構(gòu)的cxx方法辫樱,繼續(xù)跟隨.cxx_destruct峭拘,最終調(diào)用emitCXXDestructMethod函數(shù),代碼如下:

staticvoidemitCXXDestructMethod(CodeGenFunction &CGF, ObjCImplementationDecl *impl)

{

CodeGenFunction::RunCleanupsScopescope(CGF);

llvm::Value *self = CGF.LoadObjCSelf();

constObjCInterfaceDecl *iface = impl->getClassInterface();

for(constObjCIvarDecl *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(cleanupKind, self, ivar, destroyer,

cleanupKind & EHCleanup);

}

assert(scope.requiresCleanups() &&"nothing to do in .cxx_destruct?");

}

分析這段代碼以及其中調(diào)用后發(fā)現(xiàn):它遍歷當(dāng)前對象所有的實(shí)例變量(Ivars)狮暑,調(diào)用objc_storeStrong棚唆,從clang的ARC文檔上可以找到objc_storeStrong的示意代碼實(shí)現(xiàn)如下:

idobjc_storeStrong(id *object, id value){

value = [value retain];

id oldValue = *object;

*object = value;

[oldValue release];

return value;

}

在.cxx_destruct進(jìn)行形如objc_storeStrong(&ivar, null)的調(diào)用后,這個(gè)實(shí)例變量就被release和設(shè)置成nil了

注:真實(shí)的實(shí)現(xiàn)可以參考http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html2078行

自動(dòng)調(diào)用[super dealloc]的實(shí)現(xiàn)

按照上面的思路心例,自動(dòng)調(diào)用[super dealloc]也一定是CodeGen干的工作了

位于http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html492行

StartObjCMethod方法中:

if(ident->isStr("dealloc"))

EHStack.pushCleanup(getARCCleanupKind());

上面代碼可以得知在調(diào)用dealloc方法時(shí)被插入了代碼宵凌,由FinishARCDealloc結(jié)構(gòu)定義:

structFinishARCDealloc : EHScopeStack::Cleanup {

voidEmit(CodeGenFunction &CGF, Flags flags)override{

constObjCMethodDecl *method = cast(CGF.CurCodeDecl);

constObjCImplDecl *impl = cast(method->getDeclContext());

constObjCInterfaceDecl *iface = impl->getClassInterface();

if(!iface->getSuperClass()) return;

boolisCategory = isa(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);

}

};

上面代碼基本上就是向父類轉(zhuǎn)發(fā)dealloc的調(diào)用,實(shí)現(xiàn)了自動(dòng)調(diào)用[super dealloc]方法止后。

總結(jié)

ARC下對象的成員變量于編譯器插入的.cxx_desctruct方法自動(dòng)釋放

ARC下[super dealloc]方法也由編譯器自動(dòng)插入

所謂編譯器插入代碼過程需要進(jìn)一步了解瞎惫,還不清楚其運(yùn)作方式

clang的CodeGen也值得深入研究一下

References:

http://clang.llvm.org/docs/AutomaticReferenceCounting.html

http://my.safaribooksonline.com/book/programming/objective-c/9780132908641/3dot-memory-management/ch03

http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市译株,隨后出現(xiàn)的幾起案子瓜喇,更是在濱河造成了極大的恐慌,老刑警劉巖歉糜,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件乘寒,死亡現(xiàn)場離奇詭異,居然都是意外死亡匪补,警方通過查閱死者的電腦和手機(jī)伞辛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來夯缺,“玉大人蚤氏,你說我怎么就攤上這事∮欢担” “怎么了竿滨?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我于游,道長毁葱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任贰剥,我火速辦了婚禮头谜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鸠澈。我一直安慰自己柱告,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布笑陈。 她就那樣靜靜地躺著际度,像睡著了一般。 火紅的嫁衣襯著肌膚如雪涵妥。 梳的紋絲不亂的頭發(fā)上乖菱,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天,我揣著相機(jī)與錄音蓬网,去河邊找鬼窒所。 笑死,一個(gè)胖子當(dāng)著我的面吹牛帆锋,可吹牛的內(nèi)容都是我干的吵取。 我是一名探鬼主播,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼锯厢,長吁一口氣:“原來是場噩夢啊……” “哼皮官!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起实辑,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤捺氢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后剪撬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摄乒,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年残黑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了馍佑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,163評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡萍摊,死狀恐怖挤茄,靈堂內(nèi)的尸體忽然破棺而出如叼,到底是詐尸還是另有隱情冰木,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站踊沸,受9級特大地震影響歇终,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逼龟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一评凝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腺律,春花似錦奕短、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至之斯,卻和暖如春日杈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背佑刷。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工莉擒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瘫絮。 一個(gè)月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓涨冀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親麦萤。 傳聞我的和親對象是個(gè)殘疾皇子蝇裤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評論 0 9
  • 前言 2000年频鉴,伊利諾伊大學(xué)厄巴納-香檳分校(University of Illinois at Urbana-...
    星光社的戴銘閱讀 15,861評論 8 180
  • 摘自: http://www.cocoachina.com/ios/20150803/12872.html 說明...
    program袁閱讀 873評論 1 3
  • 虛偽的浮華 盛不住 流淌的青春 花繁若錦 卻是依然 無法定格 蹁躚如那年 回憶 當(dāng)初枉然 早已不見 刻舟...
    君自白衣閱讀 152評論 0 0
  • 路上奔波栓辜,手機(jī)沒電,謹(jǐn)記一份承諾垛孔。 今天要完成007的如約而至藕甩。 念奇跡30的課程,給我的變化周荐,做一個(gè)小星星的發(fā)言...
    心晴愛語閱讀 159評論 0 0