內(nèi)存管理的幾種方式
- 顯式內(nèi)存釋放(C:-free盏檐、C++:-delete)
- 內(nèi)存可能被提前釋放,(懸停指針 dangling pointer)
- 內(nèi)存永遠(yuǎn)無(wú)法釋放(內(nèi)存泄露)
- 基于引用計(jì)數(shù)(smart pointer、Objective-C)
- 垃圾回收(Java、C#)
MRC(Mannul Reference Counting)
也叫 MRR(Mannul Retain-Release)
- 嚴(yán)格遵守引用計(jì)數(shù)規(guī)則,保持 retain 和 release 之間的平衡
- 對(duì)象創(chuàng)建后音同,retain count 為1
- retain count 為正词爬,對(duì)象繼續(xù)存或
- retain count 降為0秃嗜,對(duì)象被銷毀
規(guī)則:
- 自己生成的對(duì)象,自己持有
(alloc顿膨,allocWithZone:锅锨,copy,copyWithZone:恋沃,mutableCopy必搞,mutableCopyWithZone)
- 非自己生成的對(duì)象,自己也可以持有
(retain)
- 不需要自己持有的對(duì)象囊咏,必須釋放它
(release恕洲,autorelease)
- 非自己持有的對(duì)象無(wú)法釋放
MRC 循環(huán)引用(retain cycle)
對(duì)象間直接或間接相互持有引用,自己持有自己也算梅割。
解決方法:
- assign 修飾屬性霜第,需要的地方手動(dòng)置為 nil
- block 中的循環(huán)引用,使用 __block 修飾對(duì)象
MRC 釋放時(shí)機(jī)的問(wèn)題
有時(shí)在創(chuàng)建一個(gè)對(duì)象之后户辞,無(wú)法確定在什么時(shí)候釋放泌类,比如一個(gè)方法要返回一個(gè)對(duì)象,那么在方法內(nèi)產(chǎn)生的 retain count 要在什么時(shí)候 release 是未知的底燎。
- (NSObject *)object
{
NSObject *o = [[NSObject alloc] init]; // 生成對(duì)象刃榨,retain count +1
//方法執(zhí)行完畢后要減去上面的 retain count,但是又不能在這里釋放
//[o release];
return o;
}
針對(duì)這個(gè)問(wèn)題双仍,OC 引入了 Autorelease
枢希。
Autorelease
延遲釋放對(duì)象。
方法:
- 手動(dòng)調(diào)用 autorelease 方法
- 工廠方法返回值是一般都是 autorelease 的
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *string;
char* cString = "Hello";
string = [NSString stringWithUTF8String:cString];
NSObject *obj = [[[NSObject alloc] init] autorelease];
[pool release];
autorelease 對(duì)象不會(huì)立即釋放朱沃,會(huì)被注冊(cè)到 autoreleasepool 中苞轿,在 pool 釋放時(shí),pool 中的對(duì)象會(huì)自動(dòng)調(diào)用 release。
在 iOS 應(yīng)用程序的 main 函數(shù)中呕屎,由系統(tǒng)創(chuàng)建了一個(gè) autoreleasepool让簿,在主線程的 Runloop 結(jié)束時(shí)會(huì)自動(dòng)釋放 autoreleasepool。
好處:減少內(nèi)存峰值
for (int i = 0; i < 100; i++) {
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:urlArray[i] encoding:NSUTF8StringEncoding error:&error];
}
由于工廠方法生成的對(duì)象一般都是 autorelease 的秀睛,所以這里創(chuàng)建的100個(gè)都不會(huì)立即被釋放尔当,會(huì)等到 autoreleasepool 釋放時(shí)才會(huì)釋放,這樣的話蹂安,會(huì)真用很多內(nèi)存椭迎,造成內(nèi)存峰值飆升。
解決方法是將循環(huán)體的代碼放到 autoreleasepool 中田盈。
for (int i = 0; i < 100; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSError *error;
NSString *fileContents = [NSString stringWithContentsOfURL:urlArray[i] encoding:NSUTF8StringEncoding error:&error];
[pool release];
}
這樣畜号,每次有工廠方法生成的 autorelease 對(duì)象都會(huì)被注冊(cè)到我們自己創(chuàng)建的 autreleasepool 中,在每次循環(huán)結(jié)束時(shí)釋放掉 autoreleasepool允瞧,同時(shí)也會(huì)是放掉 pool 中的 autorelease 對(duì)象简软。
相關(guān)點(diǎn):
- Run loop 會(huì)在每次 loop 到尾部時(shí)銷毀 Autorelease pool。
- GCD 的 dispatched blocks 會(huì)在一個(gè) Autorelease Pool 的上下文中執(zhí)行述暂,這個(gè) Autorelease Pool 不時(shí)的就被銷毀了(依賴于實(shí)現(xiàn)細(xì)節(jié))痹升。NSOperationQueue 也類似。
- 其他線程則會(huì)各自對(duì)自己的 Autorelease Pool 的生命周期負(fù)責(zé)畦韭。
MRC 缺點(diǎn)
- 忘記釋放疼蛾,內(nèi)存泄露
- 提前釋放,懸停指針
為了解決這些問(wèn)題艺配,Apple 在 iOS4.3 之后引入 ARC
察郁。iOS5.0 之后引入 weak
。
ARC (Automatic Reference Counting)
ARC 是 OBjective-C 編譯器的特性转唉,而不是運(yùn)行時(shí)特性或者垃圾回收機(jī)制皮钠。ARC 所做的只不過(guò)是在代碼編譯時(shí),由編譯子幫你自動(dòng)在合適的位置插入 retain/release 或 autorelease酝掩,所以本質(zhì)和 MRC 是一樣的鳞芙。
ARC 中可以 retain 的對(duì)象指針
- block 指針
- Objective-C 的對(duì)象指針
- 用
__attribute__((NSObject))
標(biāo)記的對(duì)象
編譯器會(huì)在調(diào)用如下方法(方法族)時(shí)插入 retain
alloc、new期虾、copy原朝、mutableCopy、init
ARC 環(huán)境下的關(guān)鍵字
- __strong
持有對(duì)象 - __weak
不持有對(duì)象镶苞,對(duì)象沒(méi)有強(qiáng)引用時(shí)會(huì)置為 nil - __unsafe_unretained
不持有對(duì)象喳坠,對(duì)象沒(méi)有強(qiáng)引用時(shí)不會(huì)置為 nil
可以在 C 結(jié)構(gòu)體里使用 OC 對(duì)象
struct x {
// 這里如果使用__strong 或 __weak,編譯器會(huì)報(bào)錯(cuò)
NSString __unsafe_unretained *s;
int x;
}
- __autoreleaseing
表明傳引用的參數(shù)(id *)在返回時(shí)是 autorelease 的
效果等同 MRC 下的 autorelease 方法
for (int i = 0; i < 100; i++) {
@autoreleasepool {
id __autoreleaseing obj = [[NSObject alloc] init];
NSError *error;
// 實(shí)際上在 ARC 環(huán)境下茂蚓,工廠方法已經(jīng)不在返回 autorelease 對(duì)象了壕鹉。
NSString *fileContents = [NSString stringWithContentsOfURL:urlArray[i] encoding:NSUTF8StringEncoding error:&error];
}
}
ARC 循環(huán)引用
- property: weak
- block:__weak剃幌,@weakify & @strongify
Core Foundation 的內(nèi)存管理
相關(guān)方法
CFRetain、CFRelease晾浴、CFAutorelease负乡、CCGetRetainCount
規(guī)則
- The Create - Rule
含有"Create"、"Copy"的方法創(chuàng)建的對(duì)象脊凰,需要手動(dòng)釋放抖棘。
CFTimeZoneRef CFTimeZoneCreateWithTimeIntervalFromGMT(CFAllocator allocator, CFTimeInterval ti);
CFDictionaryRef CFTimeZoneCopyAbbreviationDictionary(void);
CFBundleRef CFBundleCreate(CFAllocator allocator, CFURLRef bundleURL);
- The Get - Rule
不含有則不需要手動(dòng)釋放
CFStringRef CFAttributedStringGetString(CFAttributedStringRef aStr);
- Toll - Free Bridging
- __bridge:只是聲明類型轉(zhuǎn)變,內(nèi)存管理所有權(quán)不變狸涌;
- __bridge_retained:指針類型轉(zhuǎn)變切省,同時(shí)內(nèi)存管理所有權(quán)由 Objective-C 交由 Core Foundation 處理,即由 ARC 變?yōu)?MRC帕胆;
- __bridge_transfer:指針類型轉(zhuǎn)變朝捆,同事內(nèi)存管理所有權(quán)由 Core Foundation 交由 Objective-C 處理,即 MRC 變?yōu)?ARC懒豹;
// NSString 轉(zhuǎn)為 CFString 芙盘,內(nèi)存管理所有權(quán)不變,依然是 ARC
CFStringRef s = (__bridge CFStringRef)[[NSString alloc] initWithFormat:@"Hello, %@!", name];
NSString *s1 = [[NSString alloc] initWithFormat:@"Hello, %@!", name];
// NSString 轉(zhuǎn)為 CFString歼捐,且內(nèi)存管理所有權(quán)改變何陆, ARC 變?yōu)?MRC
CFStringRef s2 = (__bridge_retained CFStringRef)s1;
// or CFStringRef s2 = (CFStringRef)CFBridgingRetain(s1);
...
// 手動(dòng)釋放
CFRelease(s2);
CFStringRef result = CFURLCreateStringByAddingPercentEscapes(...);
// CFString 轉(zhuǎn) NSString,內(nèi)存管理所有權(quán)改變豹储, MRC 變 ARC
NSStrig *s = (__bridge_transfer)result;
// or NSStrig *s = (NSString *)CFBridgingRelease(result);
// 雖然創(chuàng)建方法含有"Create",但是無(wú)需手動(dòng)釋放
return s;
思考
1淘这、為什么 weak 變量會(huì)自動(dòng)置為 nil 剥扣?
OC 的運(yùn)行時(shí)在運(yùn)行期會(huì)維護(hù)一個(gè) weak 表(hash 表),會(huì)以對(duì)象(被 weak 指針指向的對(duì)象)的地址 作為key铝穷,以 weak 對(duì)象地址作為value钠怯,一個(gè)key可以注冊(cè)多個(gè)value。
如:__weak typeof(self) self_weak = self;
以 self 的地址作為 key曙聂,self_weak 的地址作為 value晦炊;
一個(gè) weak 對(duì)象引用計(jì)數(shù)變?yōu)?后的執(zhí)行過(guò)程:
- objc_release 此時(shí)引用計(jì)數(shù)變?yōu)?,執(zhí)行 dealloc
- _objc_rootDealloc
- object_dispose
- objc_destructInstance
-
objc_clear_deallocating
最后一個(gè)方法宁脊,會(huì)從 weak 表里獲取釋放對(duì)象的地址作為鍵值的所有記錄断国,將記錄里面 value 對(duì)應(yīng)的變量地址,賦值為 nil榆苞,然后刪除這條記錄稳衬。
2、AutoreleasePool 是怎么將 Autorelease 對(duì)象注冊(cè)到 pool 中和從 pool 中釋放坐漏?
注冊(cè)時(shí)會(huì)調(diào)用 push 方法薄疚,釋放時(shí)則會(huì)調(diào)用 pop 方法
void *objc_autoreleasePoolPush(void) {
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);
}
AutoreleasePool 類內(nèi)部實(shí)現(xiàn)是一個(gè)雙向鏈表碧信。
class AutoreleasePoolPage {
magic_t const magic;
id *next;
pthread_t const thread;
AutoreleasePoolPage *const parent;
AutoreleasePoolPage *child;
uint32_t const depth;
uint32_t const hiwat;
}
AutoreleasePool 有一個(gè)哨兵對(duì)象(POOL_SENTINEL)用來(lái)標(biāo)記嵌套關(guān)系,當(dāng)其中的一個(gè)對(duì)象釋放后遇到哨兵對(duì)象街夭,就意味著當(dāng)前的 AutoreleasePool 已經(jīng)釋放完畢了砰碴,接下來(lái)繼續(xù)釋放的對(duì)象就是另外一個(gè) AutoreleasePool 中的對(duì)象了。
3板丽、為什么 nonatomic 屬性會(huì)導(dǎo)致 crash 衣式?
在給一個(gè)屬性賦值時(shí),運(yùn)行時(shí)執(zhí)行的代碼類似如下代碼:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic)
{
id oldValue;
id *slot = (id *) ((char *)self + offset);
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t & slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
在最后會(huì)釋放舊值檐什,這時(shí)候就會(huì)存在問(wèn)題碴卧。在多線程情況下,nonatomic 屬性不是線程安全的乃正,所以會(huì)存在兩個(gè)線程同時(shí)修改屬性的情況住册,假設(shè)有兩個(gè)線程同時(shí)修改屬性,這兩個(gè)線程中的 oldValue 是指向同一塊內(nèi)存的瓮具,在線程1第一次釋放后荧飞,這塊內(nèi)存會(huì)被重新分配,此時(shí)線程2再次釋放名党,就會(huì)將新分配的變量釋放掉叹阔,之后若在別的地方用到了這個(gè)變量,就可能會(huì)發(fā)生 crash传睹。
4耳幢、Objective-C 里的數(shù)組,放進(jìn)去的元素都會(huì)被持有嗎欧啤?如果被數(shù)組持有睛藻,怎樣做一個(gè)不吃又元素的數(shù)組?
5邢隧、NSDictionary 的 key 和 value 的內(nèi)存語(yǔ)義是怎樣的店印?
6、在 ARC 環(huán)境下倒慧,函數(shù)的返回值已經(jīng)不再使用 Autorelease 機(jī)制了按摘,它是怎么實(shí)現(xiàn)的?但是如果調(diào)用方是 MRC 環(huán)境纫谅,ARC 下面的函數(shù)返回值會(huì)自動(dòng)啟動(dòng) Autorelease炫贤,這又是怎么實(shí)現(xiàn)的?
7系宜、Toll-Free Bridging 是怎么實(shí)現(xiàn)的照激?什么樣的 Core Foundation 和 Foundation 對(duì)象才能做到 Toll-Free Bridging?
8盹牧、如果一個(gè)對(duì)象持有一個(gè) block俩垃,在 block 里面使用到了對(duì)象的一個(gè)實(shí)例變量励幼,會(huì)形成循環(huán)引用嗎?如果以一個(gè)臨時(shí)變量引用實(shí)例變量口柳,在 block 中使用這個(gè)臨時(shí)變量又會(huì)怎樣苹粟?
9、對(duì)象的引用計(jì)數(shù)存在對(duì)象的內(nèi)存布局里還是全局的引用計(jì)數(shù)變里跃闹?為什么嵌削??jī)煞N方式各有什么優(yōu)缺點(diǎn)?
Q&A
- 靜態(tài)分析工具:Xcode望艺、sonar
相關(guān)參考資料
- 《Objective-C 高級(jí)編程 iOS 與 OS X 多線程和內(nèi)存管理》
- 《垃圾回收算法手冊(cè)》
- Objective-C Automatic Reference Counting (ARC)
- Advanced Memory Management Programming Guide
- Transitioning to ARC Release Notes
- Memory Management Programming Guide for Core Foundation
- 自動(dòng)釋放池的前世今生 ---- 深入解析 autoreleasepool