上一篇最后講release的時(shí)候說(shuō)到靶溜,在release的最后淮腾,當(dāng)引用計(jì)數(shù)減為0的時(shí)候就進(jìn)入了dealloc的過程鸯屿。這一篇就來(lái)講講dealloc和相關(guān)的一些方法止潮。先從dealloc的對(duì)頭alloc說(shuō)起般哼。
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方法的時(shí)候,我發(fā)現(xiàn)alloc方法的調(diào)用棧很深蒸眠,但是做的事情其實(shí)不多漾橙,中間很多方法看起來(lái)一大段,其實(shí)真正執(zhí)行的只有一小部分楞卡,所以我在展示源代碼的時(shí)候只保留了最常用的代碼霜运。
下面就來(lái)分析一下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
第一步獲取對(duì)象的大小,注釋已經(jīng)講的很清楚淘捡,對(duì)象大小最小為16藕各。并且word_align()方法確保大小是8的倍數(shù),其實(shí)就是按字節(jié)對(duì)齊焦除。
id obj = (id)calloc(1, size);
懂一點(diǎn)c的人都明白激况,這是分配一塊大小為size的內(nèi)存,并返回指向起始地址的指針膘魄。obj->initInstanceIsa(cls, hasCxxDtor);
初始化isa乌逐,這一部分的源代碼在Runtime源碼 —— 對(duì)象、類和isa這篇文章中已經(jīng)講過了创葡,這里就不說(shuō)了浙踢。return obj;
最后返回初始化好的obj。
總的來(lái)說(shuō)alloc方法還是很簡(jiǎn)單的灿渴。
init
init方法更簡(jiǎn)單:
- (id)init {
return _objc_rootInit(self);
}
id _objc_rootInit(id obj)
{
return obj;
}
簡(jiǎn)單到什么都沒做洛波。
看到這里,我就奇怪了逻杖,這init什么都不做奋岁,還要調(diào)用了干什么,只要用一個(gè)alloc不就夠了么荸百,我測(cè)試了一下:
只調(diào)用alloc闻伶,可以設(shè)置屬性,可以調(diào)用方法够话,看起來(lái)我們習(xí)慣的寫法是遠(yuǎn)古遺留的產(chǎn)物蓝翰。
new
現(xiàn)在其實(shí)這么寫的已經(jīng)不多了:
[[XXClass alloc] init];
大多數(shù)都用一個(gè)new代替了:
[XXClass new];
這個(gè)方法也很簡(jiǎn)單:
+ (id)new {
return [callAlloc(self, false/*checkNil*/) init];
}
其實(shí)就是把a(bǔ)lloc和init結(jié)合起來(lái)了,不過現(xiàn)在init什么事都不做女嘲,new和alloc方法其實(shí)是一個(gè)意思了畜份。
dealloc
自從進(jìn)入ARC時(shí)代,dealloc方法已經(jīng)不常見了欣尼,除非有CF對(duì)象需要釋放爆雹。
本文開始的時(shí)候也提到過,在release方法最后愕鼓,當(dāng)引用計(jì)數(shù)降為0的時(shí)候钙态,會(huì)調(diào)用dealloc方法釋放內(nèi)存」交危看看dealloc方法究竟做了什么事册倒。
- (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()方法中,有個(gè)if判斷磺送,條件分為5個(gè)部分:
- isa.nonpointer
是否是nonpointer驻子,基本都是 - !isa.weakly_referenced
是否被弱引用 - !isa.has_assoc
是否有關(guān)聯(lián)對(duì)象 - !isa.has_cxx_dtor
是否有c++析構(gòu)器 - !isa.has_sidetable_rc
引用計(jì)數(shù)是否曾經(jīng)溢出過
如果以上判斷條件都為真灿意,那么可以快速釋放對(duì)象,這也是為什么在Runtime源碼 —— 對(duì)象崇呵、類和isa這篇文章中介紹isa結(jié)構(gòu)體相關(guān)字段時(shí)提到過的可以快速釋放對(duì)象缤剧,就是在這里知道的。
那么大多數(shù)情況是什么樣的呢演熟?大多數(shù)情況這個(gè)判斷都是false鞭执,因?yàn)榈?點(diǎn),只需要類中聲明過屬性或者實(shí)例變量芒粹,那么就需要c++析構(gòu)函數(shù)來(lái)釋放這些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;
}
釋放對(duì)象的時(shí)機(jī)是在objc_destructInstance()方法調(diào)用之后,這個(gè)方法做的事情是銷毀這個(gè)對(duì)象存在的證據(jù)钦奋,分成3個(gè)部分銷毀:
- 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);
}
}
}
學(xué)過c++的都知道析構(gòu)函數(shù)從子類開始座云,逐層向上調(diào)用。這個(gè)方法其實(shí)就做了這個(gè)事情付材,有一點(diǎn)奇怪的就是朦拖,在我們的類中,并沒有聲明過任何析構(gòu)函數(shù)厌衔,那么查找到的析構(gòu)函數(shù)究竟是什么呢璧帝?
用代碼測(cè)試一下,先在類中增加一個(gè)屬性富寿,這樣就能進(jìn)入這個(gè)方法了:
// ZNObject.h
@interface ZNObject : NSObject
@property (nonatomic, copy) NSString *name;
@end
// ZNObject.m
@implementation ZNObject
- (void)dealloc {
NSLog(@"znobject dealloc");
}
接著添加這些代碼用來(lái)測(cè)試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;
}
挺愚蠢的睬隶,其實(shí)加個(gè)大括號(hào)就可以了。
在self.obj = nil;這行先加個(gè)斷點(diǎn)页徐,進(jìn)入斷點(diǎn)之后苏潜,在object_cxxDestructFromClass()方法中添加一個(gè)斷點(diǎn),運(yùn)行進(jìn)入斷點(diǎn)如下圖:
先看左側(cè)的調(diào)用棧变勇,當(dāng)我將self.obj設(shè)為nil的時(shí)候恤左,就進(jìn)入了release的過程。這里引用計(jì)數(shù)為0搀绣,所以繼續(xù)進(jìn)入了dealloc的過程飞袋。先調(diào)用了ZNObject類中的dealloc,打印出了log豌熄,然后就進(jìn)入了本節(jié)dealloc的流程授嘀。
看看dtor的內(nèi)容,析構(gòu)函數(shù)是一個(gè)叫做.cxx_destruct的方法锣险,但是這個(gè)方法找不到實(shí)現(xiàn)的代碼蹄皱,怎么才能看到這個(gè)方法做了什么呢览闰?
換個(gè)思路試試看,c++的析構(gòu)函數(shù)是在對(duì)象有ivar的時(shí)候才會(huì)被調(diào)用巷折,調(diào)用的目的就是為了釋放這些ivar压鉴,那么我們只需要觀察ivar的變化情況就可以了。
調(diào)整一下測(cè)試的代碼锻拘,先給屬性賦個(gè)值油吭,再運(yùn)行:
添加一個(gè)watchpoint,當(dāng)name變化的時(shí)候署拟,會(huì)自動(dòng)進(jìn)入斷點(diǎn)婉宰,直接運(yùn)行程序:
可以看到name的銷毀是在objc_storeStrong方法中進(jìn)行的,這個(gè)方法被.cxx_destroy調(diào)用推穷。遺憾的是心包,依然不能窺探到.cxx_destroy究竟做了些什么事。
講了這么多馒铃,其實(shí)就說(shuō)明了ivar釋放的過程蟹腾,下面進(jìn)入第二步。
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)對(duì)象区宇,最常見的應(yīng)用應(yīng)該就是在category中聲明屬性娃殖。這一塊這里就不展開了,后面談Associated Object的時(shí)候再說(shuō)吧议谷。
這里只要知道這一塊做的事情就是移除對(duì)象的associated object炉爆,更直接一點(diǎn)就是如果有這樣的代碼:
objc_setAssociatedObject(self.obj, @selector(obj), self.obj2, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
在self.obj對(duì)象dealloc的時(shí)候,就會(huì)進(jìn)入_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個(gè)判斷條件叶洞,part1對(duì)應(yīng)的是4和part2對(duì)應(yīng)的是3,其他3個(gè)條件就都在這個(gè)方法里啦禀崖。
第一個(gè)if判斷對(duì)應(yīng)1衩辟,因?yàn)榛径际莕onpointer,所以直接看else中的內(nèi)容波附。else中對(duì)應(yīng)了2和5兩個(gè)條件艺晴,看看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的時(shí)候有提到過掸屡,SideTable存儲(chǔ)了溢出的引用計(jì)數(shù)封寞,也與弱引用相關(guān),這里其實(shí)就是對(duì)這兩部分進(jìn)行處理仅财。關(guān)于SideTable的具體內(nèi)容就不展開了狈究。這里只需要知道:
- 如果有對(duì)此對(duì)象的弱引用,那么把所有的弱引用都置為nil盏求。weak對(duì)象設(shè)為nil原來(lái)就是在這里進(jìn)行的抖锥。
- 如果引用計(jì)數(shù)曾經(jīng)溢出過亿眠,那么SideTable中就存儲(chǔ)過相關(guān)信息,當(dāng)然在這個(gè)時(shí)間點(diǎn)磅废,引用計(jì)數(shù)的值肯定是為0的纳像,但是即使是0也不放過,還要把曾經(jīng)存在的痕跡抹除掉拯勉。感覺好殘忍呀竟趾。。宫峦。
到這里就完成了dealloc的全部過程岔帽。