來源: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)去哪兒了?
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)箱硕,下面就探究下拴竹。
通過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中添加帶變量的屬性,這也是為什么(Edit: 在ARC或MRC下都不需要remove,感謝@sagles的基情提示)ARC下沒必要remove一遍的原因
執(zhí)行objc_clear_deallocating菱属,清空引用計(jì)數(shù)表并清除弱引用表钳榨,將所有weak引用指nil(這也就是weak變量能安全置空的所在)
所以,所探尋的ARC自動(dòng)釋放實(shí)例變量的地方就在cxxDestruct這個(gè)東西里面沒跑了纽门。
上面找到的名為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)存釋放的工作
最好的辦法還是寫個(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的過程中釋放
知道了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]方法止后。
ARC下對象的成員變量于編譯器插入的.cxx_desctruct方法自動(dòng)釋放
ARC下[super dealloc]方法也由編譯器自動(dòng)插入
所謂編譯器插入代碼過程需要進(jìn)一步了解瞎惫,還不清楚其運(yùn)作方式
clang的CodeGen也值得深入研究一下