作為一名iOS開發(fā)者,相信大家對(duì)使用autoreleasepool來降低峰值內(nèi)存或多或少都有所了解吧。簡(jiǎn)單來講,如果需要在for循環(huán)中創(chuàng)建局部實(shí)例對(duì)象,而循環(huán)次數(shù)又非常大,此時(shí)就會(huì)導(dǎo)致App瞬時(shí)內(nèi)存很高。
for (int i = 0; i < 10000*1000; i++) {
NSObject *obj = [[NSObject alloc] init];
}
此時(shí),我們可以通過注冊(cè)autoreleasepool提前釋放不再使用的對(duì)象,以達(dá)到降低峰值內(nèi)的目的。
for (int i = 0; i < 10000*1000; i++) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
}
然而,這樣使用真的有效嗎?實(shí)踐是檢驗(yàn)真理的唯一標(biāo)準(zhǔn),我們還是把代碼跑起來,用事實(shí)說話。我目前能想到的驗(yàn)證方式有兩種,一種是通過instruments工具,優(yōu)點(diǎn)是操作簡(jiǎn)單,結(jié)果直觀,缺點(diǎn)是不能細(xì)粒度的跟蹤對(duì)象的生命周期;第二種方式是通過runtime源碼調(diào)試,它的優(yōu)缺點(diǎn)跟方式一正好相反,能夠細(xì)粒度的跟蹤函數(shù)調(diào)用,但是配置繁瑣,好在GitHub上有配置好的工程,可以clone下來直接使用。
我們采用方式二進(jìn)行驗(yàn)證,在NSObject *obj = [[NSObject alloc] init];和runtime源碼中的dealloc方法分別下一個(gè)斷點(diǎn),觀察obj對(duì)象的銷毀時(shí)機(jī),具體調(diào)試步驟我不再贅述,直接上結(jié)論:
在ARC環(huán)境下,通過alloc方式分配的局部對(duì)象,即使沒有autoreleasepool包裹,在其作用域結(jié)束的時(shí)候也會(huì)立即釋放
也就是說,我們上面的第二段代碼完全是畫蛇添足,沒起到任何作用。
難道我們一直以來的理解是完全錯(cuò)誤的嗎?還是說我們使用的姿勢(shì)不對(duì)?我記得第一次看到這種用法是在經(jīng)典的《effective objective-c 2.0》中,出現(xiàn)這種錯(cuò)誤的可能性幾乎為零。我們先來還是捋一捋完整流程,之所以會(huì)造成峰值內(nèi)存高,是因?yàn)閷?duì)象沒有被及時(shí)釋放,是什么導(dǎo)致對(duì)象不能及時(shí)釋放呢?是因?yàn)閷?duì)象需要在當(dāng)前runloop結(jié)束時(shí)才有機(jī)會(huì)釋放,我們知道系統(tǒng)在runloop entry時(shí)會(huì)自動(dòng)push一個(gè)自動(dòng)釋放池,在runloop即將休眠時(shí)pop舊的釋放池,并push一個(gè)新的釋放池,此時(shí)會(huì)對(duì)這期間創(chuàng)建的對(duì)象調(diào)用release,而我們自己創(chuàng)建釋放池的目的只是創(chuàng)建一個(gè)嵌套的釋放池,使得對(duì)象不再被使用時(shí)提前釋放,而不用非等到當(dāng)前runloop結(jié)束的時(shí)候。問題逐漸明朗了,其實(shí)我們要搞清楚的是哪種方式創(chuàng)建的對(duì)象在pop釋放池的時(shí)候會(huì)被release。回顧一下遙遠(yuǎn)的MRC時(shí)代我們是怎么管理對(duì)象的生命周期的,通過new、alloc、copy、mutableCopy創(chuàng)建的對(duì)象需要手動(dòng)release,其他方式創(chuàng)建的對(duì)象不需要手動(dòng)release,原因是其他方式創(chuàng)建的對(duì)象在方法返回前已經(jīng)調(diào)用了autorelease,很明顯,自動(dòng)釋放池只對(duì)調(diào)用了autorelease的對(duì)象起作用。
接下來驗(yàn)證我們的猜想,NSArray是我們開發(fā)中經(jīng)常用到的OC容器類,它有多個(gè)版本的構(gòu)造方式,既可以通過alloc、new創(chuàng)建,也可以通過arrayWithObject:等工廠方法構(gòu)建,二者區(qū)別上文已經(jīng)說過了,前者不會(huì)調(diào)用autorelease,后者在方法返回前會(huì)調(diào)用autorelease。如果我們的猜想成立,那么
1.通過alloc、new創(chuàng)建的array對(duì)象會(huì)在作用域結(jié)束時(shí)dealloc;
2.通過arrayWithObject:等工廠方法構(gòu)建的array對(duì)象不會(huì)在作用域結(jié)束時(shí)dealloc,而是要延遲到當(dāng)前runloop結(jié)束的時(shí)候,如果循環(huán)次數(shù)很大就會(huì)造成峰值內(nèi)存過高;
3.如果給通過arrayWithObject:等工廠方法構(gòu)建的array手動(dòng)加上autoreleasepool,可以在當(dāng)前釋放池pop的時(shí)候提前釋放array對(duì)象,降低峰值內(nèi)存。
代碼很簡(jiǎn)單,我就不貼了,直接把上文中的NSObject換成NSArray就行了。驗(yàn)證結(jié)果表明,我們的猜想正確。另外需要注意一點(diǎn),在ARC環(huán)境下,通過autoreleasepool降低峰值內(nèi)存只對(duì)系統(tǒng)類的工廠方法有效,對(duì)于自定義類的自定義工廠方法(注:此處及下文描述的自定義類的自定義工廠方法,均不包含通過調(diào)用系統(tǒng)基類的工廠方法來實(shí)現(xiàn)的構(gòu)造方法)是無效的,原因留給大家自己思考。
文章結(jié)論全部來自于筆者個(gè)人的猜想驗(yàn)證,如有錯(cuò)誤歡迎大家批評(píng)指正!最后,我們還是簡(jiǎn)單總結(jié)一下
在ARC環(huán)境下,通過new、alloc、copy、mutableCopy創(chuàng)建的類對(duì)象、以及自定義類的自定義工廠方法創(chuàng)建的類對(duì)象,autoreleasepool都起不到降低峰值內(nèi)存的作用;這種方式只對(duì)系統(tǒng)類非new、alloc、copy、mutableCopy構(gòu)造方法有效