我是前言
目前正在看 oc 底層的東西坛悉,看了許多大牛的博客席纽,發(fā)現(xiàn)有一些小問題:
- runtime 的版本可能跟作者當(dāng)時寫的版本不一致
- 許多方法一筆帶過瓤湘,因為基礎(chǔ)知識的薄弱看不懂。。濒憋。
- 沒有標(biāo)明蘋果文檔的出處
所以我打算解決上面的一些問題,然后重新發(fā)一版陶夜,當(dāng)然大部分的內(nèi)容還是原作者寫的 凛驮。runtime 的源碼為 objc4-646.tar.gz
版本
進(jìn)入正題
在 ARC 環(huán)境下,我們不需要主動的調(diào)用系統(tǒng)的析構(gòu)函數(shù) dealloc 就能夠完成將對象以及父類的成員變量內(nèi)存釋放掉的操作:
- (void)dealloc
{
// ... //
// 非Objc對象內(nèi)存的釋放条辟,如CFRelease(...)
// ... //
}
問題來了:
- 這個對象成員變量(ivars)的釋放操作去哪兒了黔夭?
- 沒有主動調(diào)用 [super dealloc],那么是什么時候調(diào)用這個方法的羽嫡?
ARC文檔中對dealloc過程的解釋
clang [ARC文檔](http://clang.llvm.org/docs/AutomaticReferenceCounting.html#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)用本姥,但此時實例變量(ivars)并未釋放,父類的dealloc的方法將在子類dealloc方法返回后自動調(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下對象的實例變量在根類 [NSObject dealloc] 中釋放(通常root class都是NSObject)厂僧,變量釋放順序各種不確定(一個類內(nèi)的不確定扣草,子類和父類間也不確定,也就是說不用care釋放順序)
所以颜屠,我們不需要主動調(diào)用 [super dealloc] 辰妙,系統(tǒng)會自動調(diào)用,后面我們再講這是怎么實現(xiàn)的甫窟。接下來我們來探究在根類 NSObject 析構(gòu)時發(fā)生了什么
NSObject的析構(gòu)過程
通過 runtime 源碼密浑,我們可以發(fā)現(xiàn) NSObject 調(diào)用 dealloc 時會調(diào)用 _objc_rootDealloc
(NSObject.mm 2071行) 繼而調(diào)用object_dispose
(objc-object.h 301行) 隨后調(diào)用objc_destructInstance
(objc-runtime-new.mm 6838行), 下面講一下rootDealloc
和objc_destructInstance
函數(shù)
inline void objc_object::rootDealloc()
{
assert(!UseGC);
if (isTaggedPointer()) return;
if (isa.indexed &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor)
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
64位下,isa 指針的結(jié)構(gòu):
// ...
struct {
uintptr_t indexed : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 30; // MACH_VM_MAX_ADDRESS 0x1a0000000
uintptr_t magic : 9;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
// ...
- indexed(1 bit) 0 表示普通的 isa 指針粗井,1 表示使用優(yōu)化尔破,即Tagged Pointer存儲引用計數(shù)
- has_assoc(1 bit) 表示該對象是否包含 associated object,如果沒有浇衬,則析構(gòu)(釋放內(nèi)存)時會更快
- has_cxx_dtor(1 bit) 表示該對象是否有 C++ 或 ARC 的析構(gòu)函數(shù)懒构,如果沒有,則析構(gòu)(釋放內(nèi)存)時更快
- shiftcls(30 bits) 類的指針
- magic(9 bits) 固定值為 0xd2耘擂,用于在調(diào)試時分辨對象是否未完成初始化胆剧。
- weakly_referenced(1 bit) 表示該對象是否有過 weak 對象,如果沒有醉冤,則析構(gòu)(釋放內(nèi)存)時更快
- deallocating(1 bit) 表示該對象是否正在析構(gòu)
- has_sidetable_rc(1 bit) 表示該對象的引用計數(shù)值是否過大無法存儲在 isa 指針
- extra_jc(19 bits) 表示引用計數(shù)值減一后的結(jié)果秩霍。例如,如果對象引用計數(shù)為4蚁阳,則extra_jc為3
void *objc_destructInstance(id obj)
{
if (obj) {
// Read all of the flags at once for performance.
bool cxx = obj->hasCxxDtor();
bool assoc = !UseGC && obj->hasAssociatedObjects();
bool dealloc = !UseGC;
// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
if (dealloc) obj->clearDeallocating();
}
return obj;
}
objc_destructInstance
干了三件事情:
- 執(zhí)行了一個
object_cxxDestruct
函數(shù) - 執(zhí)行
_object_remove_assocations
函數(shù)去除和這個對象 assocate 的對象(常用于類目中添加的屬性 ) - 執(zhí)行
clearDeallocating
铃绒, 清空引用計數(shù)并清除弱引用表,將所有使用__weak修飾的指向該對象的變量置為nil
所以螺捐,ARC 自動釋放實例變量的地方就在 object_cxxDestruct
這個方法里面沒跑了颠悬。
探究 object_cxxDestruct
上面找到的名為object_cxxDestruct
的方法最終成為下面的調(diào)用
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);
}
}
}
代碼的大致意思是通過繼承鏈(isa)向上遞歸調(diào)用 SEL_cxx_destruct
這個函數(shù)的函數(shù)實現(xiàn)
從這篇文章提到:
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 借用了這個方法插入代碼實現(xiàn)了自動釋放的工作。
通過實驗找出 .cxx_destruct
@interface Father : NSObject
@property (nonatomic, copy) NSString *name;
@end
@interface Son : Father
@property (nonatomic, copy) NSArray *toys;
@end
只有兩個簡單的屬性椿疗,找個地方寫簡單的測試代碼:
// start
{
// before new
Son *son = [Son new];
son.name = @"sark";
son.toys = @[@"sunny", @"xx"];
// after new
}
// gone
當(dāng)過了大括號的作用域漏峰,son 對象就會被釋放糠悼。所以在after new這行son對象初始化完成届榄,在gone這行son對象被dealloc。
本次實驗使用 NSObject+DLIntrospection 這個擴(kuò)展來作用調(diào)試工具倔喂,通過它可以輕松打出一個類的方法铝条,成員變量等。
將這個擴(kuò)展引入工程席噩,在 after new 處設(shè)置一個斷點(diǎn)班缰,在這里打印出 Son 類所有的方法名:
po [[Son class] instanceMethods]
<__NSArrayI 0x280982520>(
- (void)setToys:(id)arg0 ,
- (id)toys,
- (void).cxx_destruct
)
發(fā)現(xiàn)出現(xiàn)了.cxx_destruct
這個方法,經(jīng)過幾次實驗悼枢,發(fā)現(xiàn):
- 只有在ARC下這個方法才會出現(xiàn)(試驗代碼的情況下)
- 只有當(dāng)前類擁有實例變量時(不論是不是用property)這個方法才會出現(xiàn)埠忘,且父類的實例變量不會導(dǎo)致子類擁有這個方法
- 出現(xiàn)這個方法和變量是否被賦值,賦值成什么沒有關(guān)系
使用 watchpoint 定位內(nèi)存釋放時刻
依然在 after new 斷點(diǎn)處馒索,輸入 lldb 命令:
watchpoint set variable son->_name
將name
的變量加入watchpoint莹妒,當(dāng)這個變量被修改時會觸發(fā)trigger:
從中可以看出,在這個時刻绰上,_name 從 0x0000000104ac5048 變成了0x0000000000000000旨怠,也就是nil,趕緊看下調(diào)用棧:
發(fā)現(xiàn)果然跟到了.cxx_destruct
方法蜈块,而且是在objc_storeStrong
方法中釋放
刨根問底.cxx_destruct
知道了ARC環(huán)境下鉴腻,對象實例變量的釋放過程在 .cxx_destruct 內(nèi)完成,但這個函數(shù)內(nèi)部發(fā)生了什么百揭,是如何調(diào)用 objc_storeStrong 釋放變量的呢爽哎?
從上面的探究中知道,.cxx_destruct 是編譯器生成的代碼器一,那它很可能在clang前端編譯時完成课锌,這讓我聯(lián)想到clang的Code Generation,因為之前曾經(jīng)使用clang -rewrite-objc xxx.m時查看過官方文檔留下了些印象盹舞,于是google:
.cxx_destruct site:clang.llvm.org
結(jié)果發(fā)現(xiàn)clang的 doxygen 文檔中 CodeGenModule 模塊正是這部分的實現(xiàn)代碼产镐,cxx相關(guān)的代碼生成部分源碼在
http://clang.llvm.org/doxygen/CodeGenModule_8cpp-source.html
位于1827行,刪減掉離題部分如下:
/// 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);
}
這個函數(shù)大概作用是:獲取到 .cxx_destruct 的selector踢步,創(chuàng)建 Method癣亚,然后加入到這個類的方法列表中,最后一行的調(diào)用才是真的創(chuàng)建這個方法的實現(xiàn)获印。這個方法位于http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html 1354行述雾,包含了構(gòu)造和析構(gòu)的 cxx 方法,繼續(xù)跟隨 .cxx_destruct,最終調(diào)用 emitCXXDestructMethod
函數(shù)玻孟,代碼如下:
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?");
}
分析這段代碼以及其中調(diào)用后發(fā)現(xiàn):它遍歷當(dāng)前對象所有的實例變量(Ivars)唆缴,調(diào)用objc_storeStrong
,從clang的ARC文檔上可以找到 objc_storeStrong 的示意代碼實現(xiàn)如下:
void objc_storeStrong(id *object, id value) {
id oldValue = *object;
value = [value retain];
*object = value;
[oldValue release];
}
在 .cxx_destruct 進(jìn)行形如 objc_storeStrong(&ivar, null) 的調(diào)用后黍翎,這個實例變量就被release和設(shè)置成nil了
自動調(diào)用[super dealloc]的實現(xiàn)
按照上面的思路面徽,自動調(diào)用 [super dealloc]
也一定是 CodeGen
的工作了,位于http://clang.llvm.org/doxygen/CGObjC_8cpp_source.html 492行 StartObjCMethod
方法中:
if (ident->isStr("dealloc"))
EHStack.pushCleanup<FinishARCDealloc>(getARCCleanupKind());
上面代碼可以得知在調(diào)用dealloc
方法時被插入了代碼匣掸,由FinishARCDealloc
結(jié)構(gòu)定義:
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);
}
};
上面代碼基本上就是向父類轉(zhuǎn)發(fā)dealloc的調(diào)用趟紊,實現(xiàn)了自動調(diào)用[super dealloc]方法。
總結(jié)
- ARC下對象的成員變量在編譯器插入的
.cxx_desctruct
方法自動釋放 - ARC下[super dealloc]方法也由編譯器自動插入
- 所謂編譯器插入代碼過程需要進(jìn)一步了解碰酝,還不清楚其運(yùn)作方式
- ARC環(huán)境霎匈,對象的實例變量將在根類 NSObject 的 dealloc 方法中釋放內(nèi)存
- Father 的實例變量(如果有)將在它的
.cxx_desctruct
方法中被釋放,而 Son 的實例變量(如果有)將在它的.cxx_desctruct
方法中被釋放 - 子類在調(diào)用 dealloc 方法時會被插入代碼送爸,自動調(diào)用父類的 dealloc 方法