機(jī)制
OC采用引用計數(shù)器對內(nèi)存進(jìn)行管理,當(dāng)一個對象的引用計數(shù)(retainCount)為0,則被釋放塌西。
引用計數(shù)分為兩種:
- 手動引用計數(shù)(MRC)
// MRC代碼
NSObject * obj = [[NSObject alloc] init]; //引用計數(shù)為1
//不需要的時候
[obj release] //引用計數(shù)減1
//持有這個對象
[obj retain] //引用計數(shù)加1
//放到AutoReleasePool
[obj autorelease]//在auto release pool釋放的時候蛙紫,引用計數(shù)減1
- 自動引用計數(shù)(ARC)
比如如下ARC代碼:
NSObject * obj;
{
obj = [[NSObject alloc] init]; //引用計數(shù)為1
}
NSLog(@"%@",obj);
OC的內(nèi)存機(jī)制可以簡單概括為:誰持有(retain)誰釋放(release)皂股。retain
引用計數(shù)+1劈猪,release
反之昧甘。
我們先看看那ratain和release內(nèi)部是如何實現(xiàn)的。
retain
- (id)retain {
return ((id)self)->rootRetain();
}
inline id objc_object::rootRetain()
{
if (isTaggedPointer()) return (id)this;
return sidetable_retain();
}
可以看出retain底層是調(diào)用了sidetable_retain()
id objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];//獲取引用計數(shù)表
table.lock(); // 加鎖
size_t& refcntStorage = table.refcnts[this]; // 根據(jù)對象的引用計數(shù)
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock(); // 解鎖
return (id)this;
}
SideTable數(shù)據(jù)結(jié)構(gòu):
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
// 省略...
};
通過代碼可以出战得,SideTable擁有一個自旋鎖
充边,一個引用計數(shù)map。這個引用計數(shù)的map以對象的地址
作為key常侦,引用計數(shù)作為value
release
- (oneway void)release {
((id)self)->rootRelease();
}
inline bool objc_object::rootRelease()
{
if (isTaggedPointer()) return false;
return sidetable_release(true);
}
uintptr_t objc_object::sidetable_release(bool performDealloc)
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
SideTable& table = SideTables()[this];
bool do_dealloc = false;
table.lock(); // 加鎖
RefcountMap::iterator it = table.refcnts.find(this); // 先找到對象的地址
if (it == table.refcnts.end()) {
do_dealloc = true; //引用計數(shù)小于閾值浇冰,最后執(zhí)行dealloc
table.refcnts[this] = SIDE_TABLE_DEALLOCATING;
} else if (it->second < SIDE_TABLE_DEALLOCATING) {
// SIDE_TABLE_WEAKLY_REFERENCED may be set. Don't change it.
do_dealloc = true;
it->second |= SIDE_TABLE_DEALLOCATING;
} else if (! (it->second & SIDE_TABLE_RC_PINNED)) {
it->second -= SIDE_TABLE_RC_ONE; //引用計數(shù)減去1
}
table.unlock(); // 解鎖
if (do_dealloc && performDealloc) {
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return do_dealloc;
}
release過程:查找map,對引用計數(shù)減1聋亡,如果引用計數(shù)小于閾值肘习,則調(diào)用SEL_dealloc
自己生成的對象,自己持有
使用以下名稱開頭的方法意味著生成的對象會被自己持有坡倔,也就是內(nèi)部會對象進(jìn)行一次retain:
- alloc
- new
- copy
- mutableCopy
比如NSObject的alloc方法:
+ (id)alloc {
return _objc_rootAlloc(self);
}
// Base class implementation of +alloc. cls is not nil.
// Calls [cls allocWithZone:nil].
id
_objc_rootAlloc(Class cls)
{
return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}
// Call [cls alloc] or [cls allocWithZone:nil], with appropriate
// shortcutting optimizations.
static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{
if (slowpath(checkNil && !cls)) return nil;
#if __OBJC2__
if (fastpath(!cls->ISA()->hasCustomAWZ())) {
// No alloc/allocWithZone implementation. Go straight to the allocator.
// fixme store hasCustomAWZ in the non-meta class and
// add it to canAllocFast's summary
if (fastpath(cls->canAllocFast())) {
// No ctors, raw isa, etc. Go straight to the metal.
bool dtor = cls->hasCxxDtor();
id obj = (id)calloc(1, cls->bits.fastInstanceSize()); // 在這里漂佩,obj創(chuàng)建的時候,obj的retainCount = 1
if (slowpath(!obj)) return callBadAllocHandler(cls);
obj->initInstanceIsa(cls, dtor);
return obj;
}
else {
// Has ctor or raw isa or something. Use the slower path.
id obj = class_createInstance(cls, 0);
if (slowpath(!obj)) return callBadAllocHandler(cls);
return obj;
}
}
#endif
// No shortcuts available.
if (allocWithZone) return [cls allocWithZone:nil];
return [cls alloc];
}
對于OC提供的方法罪塔,除了上面幾種投蝉,比如說[NSMutableArray array],通過這些方法獲取到的對象并不對其進(jìn)行持有征堪,內(nèi)部會將生成的對象放入到自動釋放池上
// 取得非自己生成而且不持有的對象
id obj = [NSMutableArray array];
// 進(jìn)行retain之后瘩缆,obj持有了對象
[obj retain];
再比如如果我們定義一個方法:
- (id)object {
id obj = [[NSObject alloc] init];
// 加入自動釋放池,pool銷毀的池銷毀的同事對obj進(jìn)行release一次
[obj autorelease];
return obj;
}
無法釋放非自己持有的對象
就像上面的 id obj1 = [obj1 object]
佃蚜,obj并沒有持有對象咳榜,如果這時候我們主動調(diào)用[obj1 release]
就會發(fā)生崩潰。
還有一種情況就是已經(jīng)被釋放的對象再對其進(jìn)行release操作的時候也會發(fā)生崩潰
id obj = [[NSObject alloc] init];
[obj release];
[obj release]; // crash
ARC中常見的所有權(quán)關(guān)鍵字
assign對應(yīng)關(guān)鍵字__unsafe_unretained, 顧名思義爽锥,就是指向的對象被釋放的時候涌韩,仍然指向之前的地址,容易引起野指針氯夷。
copy對應(yīng)關(guān)鍵字__strong,只不過在賦值的時候臣樱,調(diào)用copy方法。
retain對應(yīng)__strong
-
strong對應(yīng)__strong
__strong修飾符是id類型和對象類型默認(rèn)的所有權(quán)修飾符腮考。也就是說雇毫,以下源代碼?中的id變量,實際上被附加了所有權(quán)修飾詞:
id obj = [[NSObject alloc] init];
-
weak 對應(yīng) __weak
weak是用來替代unsafe_unretained踩蔚,weak修飾符的變量(即弱引用)不持有對象棚放,所以在超出其作用域時,對象就會釋放馅闽,所以因為強(qiáng)引用而造成的循環(huán)引用飘蚯,將其中的成員變量改為弱引用馍迄,就不會發(fā)生相同情況。
在持有某若引用時局骤,若該對象被廢棄攀圈,則此弱引用將自動失效且處于nil被賦值狀態(tài)(空弱引用)。
unsafe_unretained 對應(yīng) __unsafe_unretained
unsafe unretained與weak修飾符一樣不會增加引用計數(shù)峦甩,自己生成的對象不能繼續(xù)為自己所有赘来,所以會立即釋放。
iOS4以及OS X Snow Leopard的應(yīng)用程序中凯傲,必須使用unsafe unretained修飾符來替代weak修飾符犬辰。賦值給附有__unsafe unretained修飾符變量的對象在通過該變量使用時,如果沒有確保其存在冰单,那么應(yīng)用就會崩潰幌缝。
id __unsafe_unretained obj1 = nil;
{
id __strong obj0 = [NSObject new];
obj1 = obj0;
NSLog(@"A: %@", obj1);
}
NSLog("%@", [obj1 description];);
如果像上面那樣,程序就會崩潰球凰,因為obj0被銷毀之后狮腿,obj1并不會自動置為nil。
__bridge
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
將Objective-C的對象類型用 __bridge
轉(zhuǎn)換為 void* 類型和使用 __unsafe_unretained
關(guān)鍵字修飾的變量是一樣的
__bridge
轉(zhuǎn)換中還有另外兩種轉(zhuǎn)換呕诉,分別是" __bridge_retained
轉(zhuǎn)換"和" __bridge_transfer
轉(zhuǎn)換"
-
__bridge_retained
轉(zhuǎn)換可使要轉(zhuǎn)換賦值的變量也持有所賦值的對象缘厢。
// MRC
void *p = 0;
{
id obj = [NSObject new]; // retainCount = 1
p = (__bridge_retained void *)obj; // retainCount = 2
}
NSLog(@"class=%@", [(__bridge id)p class]);
// log:class=NSObject
-
__bridge_transfer
轉(zhuǎn)換提供與次相反的動作,被轉(zhuǎn)換的變量所持有的對象在該變量被賦值給轉(zhuǎn)換目標(biāo)變量后隨之釋放甩挫。
id p = (__bridge_transfer id)p;
等效于:
// MRC
id obj = (id)p;
[obj retain];
[(id)p release];
同__bridge_retained
和retain
類似贴硫,__bridge_transfer
與release
相似。 在給id obj賦值時retain即相當(dāng)于__strong修飾符的變量伊者。
如果使用以上兩種轉(zhuǎn)換英遭,那么不是用id類型或者對象型變量也可以生成、持有以及釋放對象亦渗。雖然可以這樣做挖诸,但是ARC中并不推薦。
void *p = (__bridge_retained void *)[NSObject new];
NSLog(@"class = %@", [(__bridge id)p class]);
(void)(__bridge_transfer id)p;
和下面代碼等效
// MRC
id p = NSObject new];
NSLog(@"class = %@", [p class]);
[p release];
Objective-C對象與Core Foundation對象
Core Foundation對象主要使用在C語言編寫的Core Foundation框架中法精,并是用引用計數(shù)的對象多律。在ARC無效時,Core Foundation框架中的retain/release分別是CFRetain/CFRelease搂蜓。
因為Core Foundation對象與OC對象沒有區(qū)別狼荞,所以在MRC時,只用簡單的C語言的轉(zhuǎn)換也能實現(xiàn)互換帮碰。另外這種轉(zhuǎn)換不需要使用額外的CPU資源相味,因此也被稱為"Toll-Free Bridge"
以下函數(shù)可用于OC對象和Core Foundation對象之間的相互變換,即Toll-Free Bridge轉(zhuǎn)換:
CFTypeRef CFBridgingRetain(id x) {
return (__bridge_retained CFTypeRef) x;
}
id CFBridgingRelease(CFTypeRef x) {
return (__bridge_transfer id)x;
}
CFMutableArrayRef cfObj = NULL;
{
NSMutableArray *obj = [[NSMutableArray alloc] init];
cfObj = CFBridgingRetain(obj);
CFShow(cfObj);
NSLog(@"retainCount = %ld", CFGetRetainCount(cfObj));
}
NSLog(@"after retainCount = %ld", CFGetRetainCount(cfObj));
CFRelease(cfObj);
效果如下
還可以通過__bridge_retained來替代CFBridgingRetain
CFMutableArrayRef cfObject = (__bridge_retained CFMutableArrayRef)obj;
反過來
CFMutableArrayRef cfObj = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
NSLog(@"retainCount = %ld", CFGetRetainCount(cfObj));
id obj = CFBridgingRelease(cfObj);
// CFRelease(cfObj);
NSLog(@"after retainCount = %ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
打印出來:
和書上的結(jié)果好像不一樣殉挽,如果加上CFRelsease就正常了丰涉,這個點沒有搞清楚:
如果我們直接用__bridge_transfer進(jìn)行轉(zhuǎn)換拓巧,結(jié)果幾就過就正常了:
CFMutableArrayRef cfObj = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
NSLog(@"retainCount = %ld", CFGetRetainCount(cfObj));
//NSMutableArray *obj = CFBridgingRelease(cfObj);
NSMutableArray *obj = (__bridge_transfer NSMutableArray *)(cfObj);
//CFRelease(cfObj);
NSLog(@"after retainCount = %ld", CFGetRetainCount((__bridge CFTypeRef)(obj)));
ARC運(yùn)行時的優(yōu)化
ARC不只是在編譯時由編譯器進(jìn)行內(nèi)存管理,實際上在此基礎(chǔ)還借助了OC運(yùn)行時庫昔搂。也就是說玲销,ARC由以下工具输拇、庫實現(xiàn):
- clang(LLVM編譯器)3.0以上
- objc4 Objective-C運(yùn)行時庫493.9以上
__strong修飾符
賦值給__strong修飾的變量:
{
id __strong obj = [[NSObject alloc] init];
}
可以看作成:
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
// 調(diào)用2次objc_msgSend方法摘符,變量作用域結(jié)束時,objc_release釋放對象策吠。
// 由此看出編譯器會自動插入release操作逛裤。
--
使用alloc/new/copy/mutableCopy以外的方法:
{
id __strong obj = [NSMutableArray array];
}
可以看作:
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleaseReturnValue(obj);
objc_release(obj);
這里和上面區(qū)別主要是objc_retainAutoreleaseReturnValue
,該函數(shù)主要用于優(yōu)化程序運(yùn)行猴抹,objc_retainAutoreleaseReturnValue
的入?yún)⑹欠祷?code>注冊在autoreleasepool中的對象的方法/函數(shù)的返回值
带族。編譯器會在alloc/new/copy/mutableCopy以外
的方法調(diào)用外部插入。
上面所說的功能實現(xiàn)的時候是需要objc_retainAutoreleaseReturnValue
和objc_autoreleaseReturnValue
配合完成蟀给,任何不在alloc/new/copy/mutableCopy組中的方法必須調(diào)objc_autoreleaseReturnValue
蝙砌。 例如,NSMutableArray類方法“array”調(diào)用此函數(shù)跋理。
+ (id)array {
return [[NSMutableArray alloc] init];
}
/* pseudo code by the compiler */
+ (id) array {
id obj = objc_msgSend(NSMutableArray, @selector(alloc)); objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj);
}
任何返回添加到自動釋放池的對象的方法都將調(diào)用objc_autoreleaseReturnValue
函數(shù)择克,如上例所示。 它將一個對象添加到自動釋放池并返回前普。 但是實際上objc_autoreleaseReturnValue
不會一直注冊到自動釋放池肚邢。
objc_autoreleaseReturnValue
檢查調(diào)用者的可執(zhí)行代碼,如果代碼在調(diào)用此方法后調(diào)用objc_retainAutoreleasedReturnValue
函數(shù)拭卿,它將跳過注冊到自動釋放池骡湖,并將對象返回給調(diào)用者。 即使objc_autoreleaseReturnValue
沒有將對象注冊到自動釋放池峻厚,objc_retainAutoreleasedReturnValue
函數(shù)也可以正確地獲得這樣的對象响蕴。 通過objc_autoreleaseReturnValue
和objc_retainAutoreleasedReturnValue
的合作,對象繞過被添加到自動釋放池惠桃。
一般來說:檢驗了主調(diào)方在返回值之后是否緊接著調(diào)用了objc_retainAutoreleasedReturnValue
浦夷,如果是,就知道了外部是ARC環(huán)境刽射,反之就走沒被優(yōu)化的老邏輯军拟。
__weak修飾符
- 當(dāng)引用對象被丟棄時,__weak修飾的變量會賦值為nil誓禁。
- 通過__weak限定變量訪問對象時懈息,該對象將添加到自動釋放池。
{
id __weak obj1 = obj; // 假色obj誒__strong修飾且對象被賦值
}
/* pseudo code by the compiler */
id obj1;
objc_initWeak(&obj1, obj);
objc_destroyWeak(&obj1);
通過objc_initWeak
初始化__weak修飾的變量摹恰,在變量作用域結(jié)束時通過objc_destroyWeak
釋放該變量辫继。
objc4源碼中objc_initWeak
和objc_destroyWeak
的具體實現(xiàn)
id objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
void objc_destroyWeak(id *location)
{
(void)storeWeak<true/*old*/, false/*new*/, false/*crash*/>
(location, nil);
}
所以怒见,本質(zhì)上都是調(diào)用了storeWeak
函數(shù),storeWeak
函數(shù)把第二參數(shù)的賦值對象的地址作為鍵值
姑宽,將第一個參數(shù)的附有__weak修飾符的變量的地址注冊到weak表中遣耍。如果第二個參數(shù)為0/nil,則把變量的地址從weak表中刪炮车。
這個函數(shù)總結(jié)起來主要做了以下事情:
- 獲取存儲weak對象的map舵变,這個map的key是對象的地址,value是weak引用的地址
- 當(dāng)對象被釋放的時候瘦穆,根據(jù)對象的地址可以找到對應(yīng)的weak引用的地址纪隙,將其置為nil即可
weak表與引用計數(shù)表都是采用散列表
實現(xiàn)。另外扛或,由于一哥對象可以同時賦值給多個__waek對象修飾符的變量中绵咱,對于一個鍵值,可以注冊多個變量的地址熙兔。
釋放對象的時候悲伶,一般經(jīng)歷下面幾個操作:
- objc_release
- 因為引用計數(shù)為0所以執(zhí)行dealloc
- _objc_rootDealloc
- object_dispose
- objc_destructInstance
- objc_clear_deallocating
對象被廢棄時最后調(diào)用的objc_ckear_deallocating函數(shù)動作如下:
- 從weak表中獲取廢棄對象的地址為鍵值的記錄
- 將包含在記錄中的所有附有__weak修飾符變量的地址,賦值為nil
- 從weak表傷處該記錄
- 從應(yīng)用計數(shù)表中刪除廢棄對象的地址為鍵值的記錄
通過上面的步驟可以看出住涉,如果大量的是用__weak修飾符的變量麸锉,會對cpu資源造成相應(yīng)的消耗,一般只有在需要避免循環(huán)引用的時候是用__weak修飾符秆吵。
如果我們像上圖那樣淮椰,自己生成對象并復(fù)制給__weak變量,自己不能持有該對象纳寂,對象會馬上被回收主穗,引起編譯器警告
編譯器處理后代碼:
/* pseudo code by the compiler
雖然自己生成并持有對象,但是編譯器判斷其沒有持有責(zé)毙芜,因此被釋放
*/
id obj;
id tmp = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp, @selector(init));
objc_initWeak(&obj, tmp);
objc_release(tmp);
objc_destroyWeak(&obj);
然后再來測試一下:使用__weak修飾符的變量是否會將對象注冊到autoreleasepool忽媒。
{
id __weak obj1 = obj;
NSLog("%@", obj1);
}
/* 編譯器的模擬代碼 */
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);
與被賦值相比,在使用附有__weak修飾變量的情況下腋粥,增加了對objc_loadWeakRetained
函數(shù)和objc_autorelease
函數(shù)的調(diào)用晦雨。
-
objc_loadWeakRetained
函數(shù)取出附有__weak修飾符變量所引用的對象并ratain -
objc_autorelease
函數(shù)將對象注冊到autoreleasepool中
由于附有__weak修飾符變量所引用的對象能被注冊到autoreleasepool中,所以在@autoreleasepool塊結(jié)束前都能保證對象不被釋放隘冲。但是闹瞧,如果大流量地是用__weak變量會導(dǎo)致autoreleasepool的對象也會大量地添加,因此在使用__weak變量最好先暫時賦值給__strong變量再是用后者展辞。
這樣就能解釋我們平時用到的weak-strong-dance的原理了奥邮。以AFN的源碼為例子:
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
}
上面在閉包中利用把weakSelf賦值給strongSelf,保證在callback閉包執(zhí)行的過程中,self不會被釋放洽腺。
題外話
- 1脚粟、內(nèi)斂函數(shù)
內(nèi)聯(lián)函數(shù)是指用inline關(guān)鍵字修飾的函數(shù)。內(nèi)聯(lián)函數(shù)不是在調(diào)用時發(fā)生控制轉(zhuǎn)移蘸朋,而是在編譯時將函數(shù)體嵌入在每一個調(diào)用處核无。編譯時,類似宏替換藕坯,使用函數(shù)體替換調(diào)用處的函數(shù)名团南。參考資料
參考文章:
黑幕背后的Autorelease
自動釋放池的前世今生 ---- 深入解析 Autoreleasepool
深入理解Objective C的ARC機(jī)制