前言
通過這段時間的學習沧奴,對Objective-C的內(nèi)存管理知識做一個總結奕污。分享給大家,如有理解錯誤的地方吞鸭,還望多指正寺董。
總結從以下幾個方面來說明:
- 引用計數(shù)器
- ARC(Automatic Reference Counting):自動引用計數(shù)
- 循環(huán)引用問題
- 自動釋放池
autorelease pool
正文
1 引用計數(shù)器
1.理解引用計數(shù)器
說到引用計數(shù)器,有著iOS開發(fā)經(jīng)驗的同行一定知道刻剥,Objective-C語言內(nèi)存管理的核心就是引用計數(shù)器遮咖。
簡單來說,每個OC對象都有一個引用計數(shù)器造虏,如果想使某個對象繼續(xù)存在內(nèi)存中御吞,那就使其引用計數(shù)增加1,如果該對象使用結束漓藕,我們不希望它繼續(xù)存在于內(nèi)存中陶珠,那就使其引用計數(shù)減少1(,當該對象的引用計數(shù)等于0時候享钞,系統(tǒng)收回該對象的內(nèi)存揍诽。
- 引用計數(shù)器的工作原理
NSObject協(xié)議下聲明了一下三個方法用于操作引用計數(shù)器:
-
retain
遞增引用計數(shù); -
release
遞減引用計數(shù)栗竖; -
autorelease
待稍后清理引用釋放池
時暑脆,再遞減引用計數(shù)。
查看當前引用計數(shù)的方法是retainCount
狐肢,[obj retainCount]
添吗。
對象創(chuàng)建出來的時候,其引用計數(shù)至少為1份名。若想讓它繼續(xù)存活碟联,則調用retain
方法,若該對象不再被使用僵腺,則調用release
或者autorelease
方法鲤孵。當引用計數(shù)為0時候,該對象占用的內(nèi)存將會被回收辰如。引用計數(shù)器的工作原理大概如此普监。
2 ARC(Automatic Reference Counting):自動引用計數(shù)
- 自動引用計數(shù)(ARC)的理解
在ARC出現(xiàn)前,開發(fā)者使用引用計數(shù)需要記住何時使用retain
、release
和autorelease
鹰椒,而ARC的誕生就是為了解決這個問題。ARC省去了開發(fā)者在代碼中調用retain
呕童、release
和autorelease
精力漆际,取代了開發(fā)者內(nèi)存管理的工作。 - ARC的工作原理
Xcode的Clang編譯器帶有一個靜態(tài)分析器
夺饲,用于檢測程序中引用計數(shù)有問題的地方奸汇。例如:
if (true) {
id obj = [[SomeClass alloc] init];
[obj doSometing];
}
這段代碼在MRC環(huán)境下就會出問題,因為if
條件外obj
沒有被釋放娶靡,此處會發(fā)生內(nèi)存泄漏璧微。靜態(tài)分析器
做的就是檢測這樣的錯誤奈应。
既然靜態(tài)分析器
可以做到這些,那么可以應用這個功能贯涎,提前在程序中加入retain
、release
等操作慢洋。ARC的工作原理塘雳,就是使用了這一功能。
在ARC下普筹,代碼經(jīng)過編譯后败明,會自動為源代碼添加上相對應的操作。所以在ARC下太防,retain
,release
,autorelease
都是不允許被使用的妻顶,否則會產(chǎn)生編譯錯誤。
3.ARC的一些tips
- ARC在調用
retain
,release
,autorelease
方法的時候蜒车,并不走Objctive-C的消息派發(fā)機制讳嘱,而是直接調用底層的C語言方法。這樣做提升了性能醇王,也是retain
,release
,autorelease
方法不能被重寫的原因呢燥。 - OC語法有非常嚴格的命名規(guī)則,以下列單詞開頭的方法名:
new
alloc
copy
mutableCopy
寓娩。若方法返回對象叛氨,則ARC不會為返回的對象加上autorelease
,否則會在返回對象前為其加上autorelease
。 - 除了自動的調用
retain
,release
,autorelease
之外棘伴,ARC還能夠把互相抵消的retain
,release
,autorelease
操作簡化寞埠。若某個對象上重復多次的進行了‘retain’和‘release’操作,那么ARC有時可以成對的抵消這兩個操作焊夸。 - ARC也包含運行期組件仁连。當它檢測到某方法返回對象前,為其執(zhí)行了
autorelease
操作,之后該對象還要執(zhí)行retain
操作饭冬,那ARC就會刪除這一對操作使鹅。具體方式為,在返回對象前昌抠,不直接調用autorelease
方法患朱,而是調用objc_autoreleaseReturnValue
函數(shù),此函數(shù)會檢測當前方法返回之后即將要執(zhí)行的代碼炊苫,若發(fā)現(xiàn)那段代碼要在返回對象上執(zhí)行retain
操作裁厅,則設立一個flag不再執(zhí)行原有的autorelease
操作。同理侨艾,若方法返回一個自動釋放的對象执虹,而該對象需要被保留,那么不直接執(zhí)行retain
唠梨,而是改為執(zhí)行objc_retainAutoreleasedReturnValue
函數(shù)袋励,此函數(shù)檢測剛才設立的那個flag,若已經(jīng)設置姻成,則不執(zhí)行retain
操作插龄。
objc_autoreleaseReturnValue
和objc_retainAutoreleasedReturnValue
兩個函數(shù)的實現(xiàn)必須通過查看機器碼指令才可以判斷,所以是由編譯器的開發(fā)者完成的科展。 - 用以下修飾符修飾變量時的一些語義:
__strong
:默認語義均牢,保留此值;
__unsafe_unretained
:不保留此值才睹;
__weak
:不保留此值徘跪,但是變量可以安全使用,如果系統(tǒng)回收了該對象琅攘,那么這個變量也會被自動清空垮庐;
__autoreleasing
:在方法調用時,使用這個修飾參數(shù)值坞琴,在方法返回后哨查,該值自動釋放。 - ARC環(huán)境下剧辐,當對象被回收時寒亥,實例變量的回收問題:ARC會使用
Objctive-C++
的一項特性來清理實例變量∮兀回收Objective-C++
對象時溉奕,待回收對象會調用所有C++對象的析構函數(shù)。編譯器如果發(fā)現(xiàn)某個對象有C++對象忍啤,就會生成名為.cxx_destruct
的方法加勤。ARC借助此特性,在該方法中生成清理內(nèi)存所需要的代碼。 -
CoreFoundation
對象需要手動管理內(nèi)存鳄梅,不歸ARC管理叠国,開發(fā)者必須自己調動CFRetain
/CFRelease
。
3 循環(huán)引用問題
- 循環(huán)引用的理解
如果A對象強引用了B對象戴尸,而B對象也強引用了A對象煎饼,這就是最簡單的循環(huán)引用,兩個對象間的互相引用校赤。當系統(tǒng)要回收對象A時,由于A引用了對象B筒溃,所以對象B也需要被釋放马篮,而此時B又強引用了A,如此一來怜奖,兩個對象都不能夠釋放浑测,繼續(xù)存活于內(nèi)存中,就會出現(xiàn)內(nèi)存泄漏歪玲。這就是循環(huán)引用的問題迁央。 - 循環(huán)引用問題的解決
解決循環(huán)引用問題的最佳方式就是 弱引用。用unsafe_unretained
或者weak
修飾屬性滥崩。在語義上unsafe_unretained
和assign
等價岖圈,區(qū)別于assign
用于修飾通用類型的屬性,比如int
,float
和結構體
等钙皮,而unsafe_unretained
用于修飾對象蜂科。
例如對象A強引用了對象B,那么對象B如果弱引用了對象A短条,就不會出現(xiàn)以上的問題导匣。當系統(tǒng)回收對象A時,對象B會被回收茸时,而B對A的弱引用不會造成循環(huán)引用贡定,所以不會出現(xiàn)內(nèi)存泄漏的問題。
weak
等價于unsafe_unretained
可都,它們的不同主要表現(xiàn)在被修飾的屬性被釋放后的行為不同缓待。當用unsafe_unretained
修飾的屬性被回收后,該屬性任然指向那個被回收的屬性汹粤,而weak
則指向nil命斧。使用weak
會使程序更加安全一些。 -
block
使用中的循環(huán)引用問題
這個問題在很多的技術文章中被提到過嘱兼,這里也做個簡單的說明国葬。
例如:
//DemoViewController.m
@interface DemoViewController ()
@property (nonatomic, copy) void (^testBlock) (void);
@end
@implementation DemoViewController
...
- (void)viewDidLoad {
[super viewDidLoad];
[self test];
}
- (void)test {
self.testBlock = ^(){
[self doSometing];
}
}
...
@end
以上這段代碼,由于testBlock
塊中捕獲了self
,所以testBlock
強引用了self
,而同時self
強引用著testBlock
,如此就形成了循環(huán)引用汇四,有內(nèi)存泄漏的風險接奈。
打破這種保留的方式很簡單,使用__weak
定義一個新的weakSelf
供testBlock
捕獲通孽。如下:
//DemoViewController.m
@interface DemoViewController ()
@property (nonatomic, copy) void (^testBlock) (void);
@end
@implementation DemoViewController
...
- (void)viewDidLoad {
[super viewDidLoad];
[self test];
}
- (void)test {
__weak typeof(self) weakSelf = self;
self.testBlock = ^(){
[weakSelf doSometing];
}
}
...
@end
關于block
的知識點總結序宦,會在后面整理一份。
4 自動釋放池autorelease pool
- 自動釋放池的認識
在ARC中背苦,自動釋放池(autorelease pool
)是一項重要的特性互捌。
當某個對象調用release
時,會理解遞減引用計數(shù)retainCount
行剂。如果換做調用autorelease
,則對象會被加入autorelease pool
中秕噪,當清空自動釋放池autorelease pool
時,會向其中的對象發(fā)送release
消息厚宰。 - 自動釋放池的使用
使用自動釋放池的語法如下:
//使用語法
@autoreleasepool{
//...
}
一般情況下腌巾,系統(tǒng)創(chuàng)建的主線程或者GCD機制中的線程,都會默認創(chuàng)建自己的自動釋放池铲觉,每次執(zhí)行 事件循環(huán) 時澈蝙,就會將其清空。因此撵幽,不需要自己來創(chuàng)建 自動釋放池塊灯荧。
應用程序的入口int main()
函數(shù)處,就為我們手動創(chuàng)建了應用程序的自動釋放池盐杂。
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
所以通常情況下我們無需自己創(chuàng)建自動釋放池漏麦。當某些臨時產(chǎn)生的對象導致應用程序的內(nèi)存峰值過高時,我們可以通過創(chuàng)建自動釋放池况褪,來解決這個問題撕贞。
例如:
NSMutableArray *objsArray = [NSMutableArray array];
for (int i = 0; i < 10000; i ++) {
id obj = [self createSomeObjcWithi:i];
[objsArray addObject:obj];
}
以上代碼就會造成程序的內(nèi)存突然暴增,而等所有obj
對象都釋放以后测垛,又突然下降捏膨。
此時,增加一個自動釋放池代碼塊即可解決這個問題:
NSMutableArray *objsArray = [NSMutableArray array];
for (int i = 0; i < 10000; i ++) {
@atuoreleasepool {
id obj = [self createSomeObjcWithi:i];
[objsArray addObject:obj];
}
}
這樣一來食侮,應用程序在執(zhí)行循環(huán)時号涯,就會有效降低內(nèi)存峰值,不像原來那么高锯七。
創(chuàng)建自動釋放池本身也會占用一定的內(nèi)存链快,所以是否使用自動釋放池完全取決于程序本身。
關于自動釋放池Draveness大神的自動釋放池的前世今生 ---- 深入解析 Autoreleasepool有詳細的解析眉尸。
總結
內(nèi)存管理是應用程序的靈魂域蜗,雖然在ARC環(huán)境下巨双,我們可以盡量少的投入精力在內(nèi)存管理上,但是了解其中的原理和機制霉祸,會讓我們在程序出問題時找到有效的解決途徑筑累,更是提高自我的一種方式。
當然丝蹭,內(nèi)存管理涉及到的也不止文中提到的內(nèi)容慢宗,還有很多需要挖掘的地方。
文章是本人看書學習中的總結奔穿,主要用于知識鞏固镜沽,順便和大家分享交流,如果有不妥的地方贱田,歡迎指正淘邻。