1. 引用計數(shù)(Reference Count)
- 也叫保留計數(shù)(retain count),表示對象被引用的次數(shù)。一個簡單而有效的管理對象生命周期的方式
- C++11中的智能指針,微軟的COM,OC都是使用這個技術(shù)來實現(xiàn)內(nèi)存管理的
- oc中,每一個對象都會有一個記錄引用次數(shù)的屬性(retainCount),可以用[object valueForKey:@"retainCount"]等方式獲取對象的引用計數(shù)
2. OC內(nèi)存管理三個進程(針對Cocoa類,CFType不包含在內(nèi))
MRC中內(nèi)存管理規(guī)則:
- alloc ,new創(chuàng)建一個對象obj1,會自動讓對象的引用計數(shù)為1
- 當(dāng)我們需要用一個新的指針pointer1指向上面創(chuàng)建的對象obj1的時候,除了賦值,還需要手動調(diào)用[obj1 retain]或者[obj1 copy]手動修改對象的引用計數(shù)加1
- 在要超出指針pointer1的作用域的時候,我們需要讓手動調(diào)用[pointer1 release],讓引用計數(shù)減1
autorelease和Autoreleasepool:
- 參考資料1- Objective-C Autorelease Pool 的實現(xiàn)原理
- 參考資料2 - 黑幕背后的Autorelease
- Autoreleasepool使用場景
- autoreleased 對象釋放時機: 是在當(dāng)前的runloop迭代結(jié)束時釋放的,而它能夠釋放的原因是系統(tǒng)在每個runloop迭代中都加入了自動釋放池Push和Pop
Autoreleasepool理解
- 每一個線程的 autoreleasepool 其實就是一個指針的堆棧;
- 每一個指針代表一個需要 release 的對象或者 POOL_SENTINEL(哨兵對象,代表一個 autoreleasepool 的邊界);
- 一個 pool token 就是這個 pool 所對應(yīng)的 POOL_SENTINEL 的內(nèi)存地址。當(dāng)這個 pool 被 pop 的時候,所有內(nèi)存地址在 pool token 之后的對象都會被 release ;
- 這個堆棧被劃分成了一個以 page 為結(jié)點的雙向鏈表。pages 會在必要的時候動態(tài)地增加或刪除;
- ARC下,我們使用@autoreleasepool{} 來使用一個AutoreleasePool,隨后編譯器將其改寫成下面的樣子,而這兩個函數(shù)都是對AutoreleasePoolPage的簡單封裝,所以自動釋放機制的核心就在于這個類
// {}中的代碼
void *context = objc_autoreleasePoolPush();
objc_autoreleasePoolPop(context);
- AutoreleasePool并沒有單獨的結(jié)構(gòu),而是由若干個AutoreleasePoolPage以雙向鏈表
的形式組合而成 -
一個空的 AutoreleasePoolPage 的內(nèi)存結(jié)構(gòu)如下圖所示:
image.png
magic 用來校驗 AutoreleasePoolPage 的結(jié)構(gòu)是否完整;
next 指向最新添加的 autoreleased 對象的下一個位置,初始化時指向 begin() ;
thread 指向當(dāng)前線程,所以AutoreleasePool是按線程一一對應(yīng)的
parent 指向父結(jié)點,第一個結(jié)點的 parent 值為 nil ;
child 指向子結(jié)點,最后一個結(jié)點的 child 值為 nil ;
depth 代表深度,從 0 開始,往后遞增 1;
hiwat 代表 high water mark 。
另外,當(dāng) next == begin() 時,表示 AutoreleasePoolPage 為空;當(dāng) next == end() 時,表示 AutoreleasePoolPage 已滿。
ARC下的內(nèi)存管理:
- 底層依然是引用計數(shù)的東西,只不過編譯器幫我們在適當(dāng)?shù)奈恢锰砑覯RC中管理引用計數(shù)的代碼而已
-
編譯器會在編譯階段以恰當(dāng)?shù)臅r間與地方給我們填上原本需要手寫的retain、release、autorelease等內(nèi)存管理代碼,所以ARC并非運行時的特性,也不是如java中的GC運行時的垃圾回收系統(tǒng);因此,我們也可以知道,ARC其實是處于編譯器的特性。 - ARC是編譯器的特性,但也包含了運行期組件,所執(zhí)行的優(yōu)化很有意義。解釋如下:原文 鏈接
image.png
3. CoreFoundation的內(nèi)存管理
Core Foundation 對象必須使用CFRetain和CFRelease來進行內(nèi)存管理。
實際上 Core Foundation 對象使用的 CFRetain 和 CFRelease 方法,可以認(rèn)為與 Objective-C 對象的 retain 和 release 方法等價,所以我們可以以 MRC 的方式進行類似管理,有一個小習(xí)慣注意養(yǎng)成CFRelease (cfobj)之前,判斷cfobj是否為nil,不為nil時再調(diào)用release
當(dāng)使用Objective-C 和 Core Foundation 對象相互轉(zhuǎn)換的時候,怎么處理?
必須讓編譯器知道,到底由誰來負(fù)責(zé)釋放對象,是否交給ARC處理。只有正確的處理,才能避免內(nèi)存泄漏和double free導(dǎo)致程序崩潰。
__bridge:只做類型轉(zhuǎn)換,不
修改相關(guān)對象的引用計數(shù),不修改所有權(quán). 例如:原來對象是 Core Foundation ,那么對象在不用時,需要調(diào)用 CFRelease 方法。
__bridge_retained:類型轉(zhuǎn)換后,將相關(guān)對象的引用計數(shù)加1
__bridge_transfer:類型轉(zhuǎn)換后,將相關(guān)對象的引用計數(shù)交給對方權(quán)限管理
我們根據(jù)具體的業(yè)務(wù)邏輯,合理使用上面的三種轉(zhuǎn)換關(guān)鍵字,就可以解決 Core Foundation 對象與 Objective-C 對象相對轉(zhuǎn)換的問題了。
4.其他小知識點:
- alloc ,new,copy,retain會讓引用計數(shù)器加1 , 用release,autorelease對引用計數(shù)器做減1操作
- MRC中一定要在delloc中對對象做一次release,然后最后調(diào)用super dealloc;ARC中不需要,也不能調(diào)用super dealloc
- ARC
- 只支持cocoa框架下面的對象,也就是所以繼承自NSObjec的類的實例對象
- 不支持CoreFoundation框架下面的東西,CF的內(nèi)存管理,需要手動管理,調(diào)用CFRelease(<#CFTypeRef cf#>) 和CFRetain(<#CFTypeRef cf#>)等方法管理
5.MRC下寫set,init等方法:
- (instancetype)initWithName:(NSString *)name dog:(Dog *)dog
{
if (self = [super init]) {
// init方法中不需要判斷_name和name是否不同,因為只會在初始化的時候調(diào)用一次
_name = [name copy];
_dog = [dog retain];
}
return self;
}
- (void)setDog:(Dog *)dog
{
if (_dog != dog) {
// 因為用不到舊的dog了,所以對舊的dog做一次release,
[_dog release];
// 要強引用新的dog,對新的dog做一次retain,
_dog = [dog retain];
}
}
- (void)setAge:(NSInteger)age
{
_age = age; // 基本數(shù)據(jù)類型,不需要自己管理內(nèi)存
}
- (NSString *)name
{
return _name;
}
// MRC中一定要在delloc中對對象做一次release,然后最后調(diào)用super dealloc;ARC中不需要,也不能調(diào)用super dealloc
- (void)dealloc
{
[_dog release];
[_name release];
[super dealloc];
}

