上一篇最后講release的時候說到,在release的最后,當引用計數(shù)減為0的時候就進入了dealloc的過程。這一篇就來講講dealloc和相關(guān)的一些方法。先從dealloc的對頭alloc說起。
alloc
關(guān)于alloc,最常見的用法應(yīng)該算是:
[[XXClass alloc] init];
方法alloc的調(diào)用棧是這樣的:
+ (id)alloc {
return _objc_rootAlloc(self);
}
id _objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
static id callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
return class_createInstance(cls, 0);
}
id class_createInstance(Class cls, size_t extraBytes)
{
return _class_createInstanceFromZone(cls, extraBytes, nil);
}
static id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone,
bool cxxConstruct = true,
size_t *outAllocatedSize = nil)
{
size_t size = cls->instanceSize(extraBytes);
id obj = (id)calloc(1, size);
obj->initInstanceIsa(cls, hasCxxDtor);
return obj;
}
在深入alloc方法的時候,我發(fā)現(xiàn)alloc方法的調(diào)用棧很深,但是做的事情其實不多,中間很多方法看起來一大段,其實真正執(zhí)行的只有一小部分,所以我在展示源代碼的時候只保留了最常用的代碼。
下面就來分析一下alloc方法最深處的_class_createInstanceFromZone()方法。
- size_t size = cls->instanceSize(extraBytes);
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
uint32_t unalignedInstanceSize() {
return data()->ro->instanceSize;
}
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
# define WORD_MASK 7UL
第一步獲取對象的大小,注釋已經(jīng)講的很清楚,對象大小最小為16。并且word_align()方法確保大小是8的倍數(shù),其實就是按字節(jié)對齊。
id obj = (id)calloc(1, size);
懂一點c的人都明白,這是分配一塊大小為size的內(nèi)存,并返回指向起始地址的指針。obj->initInstanceIsa(cls, hasCxxDtor);
初始化isa,這一部分的源代碼在Runtime源碼 —— 對象、類和isa這篇文章中已經(jīng)講過了,這里就不說了。return obj;
最后返回初始化好的obj。
總的來說alloc方法還是很簡單的。
init
init方法更簡單:
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
return obj;
}
簡單到什么都沒做。
看到這里,我就奇怪了,這init什么都不做,還要調(diào)用了干什么,只要用一個alloc不就夠了么,我測試了一下:

只調(diào)用alloc,可以設(shè)置屬性,可以調(diào)用方法,看起來我們習慣的寫法是遠古遺留的產(chǎn)物。
new
現(xiàn)在其實這么寫的已經(jīng)不多了:
[[XXClass alloc] init];
大多數(shù)都用一個new代替了:
[XXClass new];
這個方法也很簡單:
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
其實就是把alloc和init結(jié)合起來了,不過現(xiàn)在init什么事都不做,new和alloc方法其實是一個意思了。
dealloc
自從進入ARC時代,dealloc方法已經(jīng)不常見了,除非有CF對象需要釋放。
本文開始的時候也提到過,在release方法最后,當引用計數(shù)降為0的時候,會調(diào)用dealloc方法釋放內(nèi)存??纯磀ealloc方法究竟做了什么事。
- (void)dealloc {
_objc_rootDealloc(self);
}
void _objc_rootDealloc(id obj)
{
obj->rootDealloc();
}
inline void objc_object::rootDealloc()
{
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
free(this);
}
else {
object_dispose((id)this);
}
}
在最后的rootDealloc()方法中,有個if判斷,條件分為5個部分:
- isa.nonpointer
是否是nonpointer,基本都是 - !isa.weakly_referenced
是否被弱引用 - !isa.has_assoc
是否有關(guān)聯(lián)對象 - !isa.has_cxx_dtor
是否有c++析構(gòu)器 - !isa.has_sidetable_rc
引用計數(shù)是否曾經(jīng)溢出過
如果以上判斷條件都為真,那么可以快速釋放對象,這也是為什么在Runtime源碼 —— 對象、類和isa這篇文章中介紹isa結(jié)構(gòu)體相關(guān)字段時提到過的可以快速釋放對象,就是在這里知道的。
那么大多數(shù)情況是什么樣的呢?大多數(shù)情況這個判斷都是false,因為第4點,只需要類中聲明過屬性或者實例變量,那么就需要c++析構(gòu)函數(shù)來釋放這些ivars。所以想要快速釋放,要求還挺高。
那就看看慢速釋放的過程吧:
object_dispose((id)this);
id object_dispose(id obj)
{
objc_destructInstance(obj);
free(obj);
return nil;
}
void *objc_destructInstance(id obj)
{
if (obj) {
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
釋放對象的時機是在objc_destructInstance()方法調(diào)用之后,這個方法做的事情是銷毀這個對象存在的證據(jù),分成3個部分銷毀:
- object_cxxDestruct(obj);
- _object_remove_assocations(obj);
- obj->clearDeallocating();
part1
object_cxxDestruct(obj);
void object_cxxDestruct(id obj)
{
object_cxxDestructFromClass(obj, obj->ISA());
}
static void object_cxxDestructFromClass(id obj, Class cls)
{
void (*dtor)(id);
// Call cls's dtor first, then superclasses's dtors.
for ( ; cls; cls = cls->superclass) {
if (!cls->hasCxxDtor()) return;
dtor = (void(*)(id))
lookupMethodInClassAndLoadCache(cls, SEL_cxx_destruct);
if (dtor != (void(*)(id))_objc_msgForward_impcache) {
(*dtor)(obj);
}
}
}
學過c++的都知道析構(gòu)函數(shù)從子類開始,逐層向上調(diào)用。這個方法其實就做了這個事情,有一點奇怪的就是,在我們的類中,并沒有聲明過任何析構(gòu)函數(shù),那么查找到的析構(gòu)函數(shù)究竟是什么呢?
用代碼測試一下,先在類中增加一個屬性,這樣就能進入這個方法了:
// ZNObject.h
@interface ZNObject : NSObject
@property (nonatomic, copy) NSString *name;
@end
// ZNObject.m
@implementation ZNObject
- (void)dealloc {
NSLog(@"znobject dealloc");
}
接著添加這些代碼用來測試dealloc的過程:
// ViewController.m
@interface ViewController()
@property (nonatomic, strong) ZNObject *obj;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.obj = [ZNObject new];
}
- (void)viewDidAppear {
self.obj = nil;
}
挺愚蠢的,其實加個大括號就可以了。
在self.obj = nil;這行先加個斷點,進入斷點之后,在object_cxxDestructFromClass()方法中添加一個斷點,運行進入斷點如下圖:

先看左側(cè)的調(diào)用棧,當我將self.obj設(shè)為nil的時候,就進入了release的過程。這里引用計數(shù)為0,所以繼續(xù)進入了dealloc的過程。先調(diào)用了ZNObject類中的dealloc,打印出了log,然后就進入了本節(jié)dealloc的流程。
看看dtor的內(nèi)容,析構(gòu)函數(shù)是一個叫做.cxx_destruct的方法,但是這個方法找不到實現(xiàn)的代碼,怎么才能看到這個方法做了什么呢?
換個思路試試看,c++的析構(gòu)函數(shù)是在對象有ivar的時候才會被調(diào)用,調(diào)用的目的就是為了釋放這些ivar,那么我們只需要觀察ivar的變化情況就可以了。
調(diào)整一下測試的代碼,先給屬性賦個值,再運行:

添加一個watchpoint,當name變化的時候,會自動進入斷點,直接運行程序:

可以看到name的銷毀是在objc_storeStrong方法中進行的,這個方法被.cxx_destroy調(diào)用。遺憾的是,依然不能窺探到.cxx_destroy究竟做了些什么事。
講了這么多,其實就說明了ivar釋放的過程,下面進入第二步。
part2
_object_remove_assocations(obj);
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
關(guān)于關(guān)聯(lián)對象,最常見的應(yīng)用應(yīng)該就是在category中聲明屬性。這一塊這里就不展開了,后面談Associated Object的時候再說吧。
這里只要知道這一塊做的事情就是移除對象的associated object,更直接一點就是如果有這樣的代碼:
objc_setAssociatedObject(self.obj, @selector(obj), self.obj2, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
在self.obj對象dealloc的時候,就會進入_object_remove_assocations(obj)方法。
part3
obj->clearDeallocating();
inline void objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
clearDeallocating_slow();
}
assert(!sidetable_present());
}
在這一節(jié)最開始提到了一共5個判斷條件,part1對應(yīng)的是4和part2對應(yīng)的是3,其他3個條件就都在這個方法里啦。
第一個if判斷對應(yīng)1,因為基本都是nonpointer,所以直接看else中的內(nèi)容。else中對應(yīng)了2和5兩個條件,看看clearDeallocating_slow()是怎么做的:
void objc_object::clearDeallocating_slow()
{
SideTable& table = SideTables()[this];
if (isa.weakly_referenced) {
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
}
這里再次出現(xiàn)了SideTables,在上一篇講release的時候有提到過,SideTable存儲了溢出的引用計數(shù),也與弱引用相關(guān),這里其實就是對這兩部分進行處理。關(guān)于SideTable的具體內(nèi)容就不展開了。這里只需要知道:
- 如果有對此對象的弱引用,那么把所有的弱引用都置為nil。weak對象設(shè)為nil原來就是在這里進行的。
- 如果引用計數(shù)曾經(jīng)溢出過,那么SideTable中就存儲過相關(guān)信息,當然在這個時間點,引用計數(shù)的值肯定是為0的,但是即使是0也不放過,還要把曾經(jīng)存在的痕跡抹除掉。感覺好殘忍呀。。。
到這里就完成了dealloc的全部過程。