該文章屬于劉小壯原創(chuàng),轉(zhuǎn)載請(qǐng)注明:劉小壯

之前寫過一篇關(guān)于removeFromSuperview方法處理的文章,寫完后一直就沒怎么更新這篇文章。這兩天回過頭來看看,感覺這篇文章有些地方寫的不夠嚴(yán)謹(jǐn),而且還有一些自己理解錯(cuò)的地方,所以打算重寫這篇文章。
在使用removeFromSuperview方法的時(shí)候,發(fā)現(xiàn)這個(gè)方法有很多我們沒有注意的地方。而且對(duì)于一些不規(guī)范的操作,蘋果也對(duì)其進(jìn)行了容錯(cuò)處理。所以我對(duì)removeFromSuperview的一些使用細(xì)節(jié)整理了一下,包括ARC和MRC兩種情況。其中還會(huì)簡(jiǎn)單涉及一些內(nèi)存管理相關(guān)的部分,文章中有什么問題,還希望多多指出,謝謝!??
測(cè)試環(huán)境
| 運(yùn)行環(huán)境 | 版本號(hào) |
|---|---|
| Xcode | 7.1 |
| Mac OS X | 10.11 |
| iOS | 9.3.1 |
| 硬件設(shè)備 | iPhone 5S |
視圖結(jié)構(gòu)
在iOS應(yīng)用中,視圖的結(jié)構(gòu)是樹型數(shù)據(jù)結(jié)構(gòu),以這種結(jié)構(gòu)來控制視圖顯示,這種數(shù)據(jù)結(jié)構(gòu)有一個(gè)很好的優(yōu)點(diǎn):
層級(jí)關(guān)系分明,并且方便傳遞事件。從根節(jié)點(diǎn)出發(fā),通過葉節(jié)點(diǎn)向下擴(kuò)展,同一枝的上一個(gè)節(jié)點(diǎn)就是下一個(gè)節(jié)點(diǎn)的superview,下一個(gè)節(jié)點(diǎn)就是上一個(gè)節(jié)點(diǎn)的subview。每個(gè)應(yīng)用程序都有一個(gè)主window,這個(gè)window就是根節(jié)點(diǎn)。
removeFromSuperview
每一個(gè)View都和視圖結(jié)構(gòu)以及響應(yīng)者鏈有直接的關(guān)系,但是這篇文章不打算著重的講這兩個(gè)方面,主要講removeFromSuperview方法。將當(dāng)前視圖從其父視圖移除,需要調(diào)用removeFromSuperview方法。下面是蘋果對(duì)于這個(gè)API的官方定義:
Unlinks the receiver from its superview and its window, and removes it from the responder chain.
譯:把當(dāng)前View從它的父View和窗口中移除,同時(shí)也把它從響應(yīng)事件操作的響應(yīng)者鏈中移除。
removeFromSuperview就是一個(gè)視圖節(jié)點(diǎn)刪除的操作,執(zhí)行這個(gè)方法,就等于在樹形結(jié)構(gòu)中找到該節(jié)點(diǎn),從樹型數(shù)據(jù)結(jié)構(gòu)中刪除該節(jié)點(diǎn)及其子節(jié)點(diǎn),而并非只是刪除該節(jié)點(diǎn)自己。同時(shí),另一個(gè)操作就是把該對(duì)象從響應(yīng)者鏈中移除。
執(zhí)行removeFromSuperview方法后,會(huì)從父視圖中移除,并且將Superview對(duì)視圖的強(qiáng)引用刪除,此時(shí)如果沒有其他地方再對(duì)視圖進(jìn)行強(qiáng)引用,則會(huì)從內(nèi)存中移除。如果還存在其他強(qiáng)引用,視圖只是不在屏幕中顯示,并沒有將該視圖從內(nèi)存中移除。所以如果需要使用該視圖,不需要再次創(chuàng)建,而是直接addSubview就可以了。
對(duì)于這個(gè)API,蘋果并沒有給出過多的解釋,只是簡(jiǎn)單的描述了一下這個(gè)API,以及說明了這個(gè)API的注意點(diǎn)。所以,下面將會(huì)根據(jù)我的使用經(jīng)驗(yàn),繼續(xù)講解這個(gè)API。
內(nèi)存管理
方法調(diào)用后的內(nèi)存管理
經(jīng)過測(cè)試,在ARC的情況下執(zhí)行removeFromSuperview方法多次也沒有問題,因?yàn)?strong>ARC內(nèi)存是系統(tǒng)為我們管理的。
但是在MRC中,根據(jù)官方API的說明:
If the view’s superview is not nil, the superview releases the view.
也就是每執(zhí)行一次removeFromSuperview方法,方法內(nèi)部都會(huì)執(zhí)行一次release操作。但是經(jīng)過我的測(cè)試,發(fā)現(xiàn)調(diào)用removeFromSuperview方法后,引用計(jì)數(shù)并沒有減少,反而增加了一個(gè)。(我是通過調(diào)用retainCount查看的引用計(jì)數(shù),但是并不是真正準(zhǔn)確的,后面會(huì)講解這個(gè)問題)
內(nèi)存陷阱
那如果是這樣,那就遇到一個(gè)和我們之前認(rèn)知不太相同的答案了。具體是什么問題,還是需要自己寫代碼驗(yàn)證,于是我基于上面描述的測(cè)試環(huán)境,寫了一些關(guān)于視圖的測(cè)試代碼。
UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
[self.view addSubview:view];
[view release];
[view removeFromSuperview];
// 多次調(diào)用remove方法
[view removeFromSuperview];
經(jīng)過我的測(cè)試發(fā)現(xiàn),調(diào)用removeFromSuperview方法后引用計(jì)數(shù)并沒有增加,調(diào)用完之后還是會(huì)release的。我們之前看到的引用計(jì)數(shù)的增加,是因?yàn)橄到y(tǒng)的隱藏操作導(dǎo)致的。之前在MRC時(shí)期經(jīng)常發(fā)現(xiàn)retainCount不準(zhǔn)確,這主要是因?yàn)?strong>iOS系統(tǒng)API的引用、或自動(dòng)釋放池導(dǎo)致的,所以retainCount并不能當(dāng)做可靠的參考。
所以,如果調(diào)用多個(gè)release,還是會(huì)崩潰的,始終要相信iOS的MRC內(nèi)存管理原則,這才是可靠的??梢远啻握{(diào)用removeFromSuperview方法,在已經(jīng)移除父視圖后,其他多余的調(diào)用不會(huì)改變?nèi)魏我糜?jì)數(shù)。對(duì)于addSubview:方法也是一樣的,下面會(huì)講這個(gè)方法。
使用細(xì)節(jié)
多次執(zhí)行addSubview:操作
假設(shè)現(xiàn)在有ViewA、ViewB、ViewC三個(gè)視圖,ViewA添加到ViewB之后又要添加到ViewC上面,此時(shí)ViewA同時(shí)執(zhí)行了向ViewB、ViewC兩個(gè)視圖addSubview:的操作。但是因?yàn)橹挥幸粋€(gè)視圖對(duì)象,所以只會(huì)以最后一次添加的為準(zhǔn),第一次執(zhí)行的添加到ViewB的操作是無效的。通過打印兩個(gè)View的子視圖可以看到,只有最后執(zhí)行的添加到ViewC上的操作才是有效的,ViewC才真正擁有了ViewA,而ViewB的子視圖是空的。
一個(gè)視圖不只是向其他多個(gè)頁面進(jìn)行添加操作不會(huì)出現(xiàn)問題,而且向同一個(gè)視圖上執(zhí)行多次添加操作也是沒有問題的,并不會(huì)導(dǎo)致視圖被多次添加的問題,也不需要在添加之前進(jìn)行removeFromSuperview操作,這個(gè)是在MRC和ARC都是有效的。因?yàn)橄到y(tǒng)在addSubview:方法中進(jìn)行了一些判斷操作,如果當(dāng)前視圖已經(jīng)添加到其他視圖,會(huì)將當(dāng)前視圖從其他視圖中移除,然后執(zhí)行添加操作。如果當(dāng)前視圖已經(jīng)添加到這個(gè)視圖中,就不會(huì)再次執(zhí)行添加操作。
一個(gè)小坑
其實(shí)也說不上是坑,可以算是一個(gè)了解的知識(shí)點(diǎn)吧。在ARC或MRC的情況下,調(diào)用removeFromSuperview和addSubview:方法其中之一,都需要在另一個(gè)方法已經(jīng)執(zhí)行的情況下才會(huì)有效,對(duì)于多次執(zhí)行一個(gè)同方法系統(tǒng)也是有判斷操作的,并不會(huì)被執(zhí)行多次。
例如調(diào)用remove方法之后,此時(shí)視圖已經(jīng)不在父視圖之上了,在多次調(diào)用這個(gè)方法是不起作用的,而且MRC下引用計(jì)數(shù)也不會(huì)被減少多次。對(duì)于addSubview:方法也是一樣的,向同一個(gè)父視圖上添加子視圖,不會(huì)被重復(fù)添加,添加之后引用計(jì)數(shù)也不會(huì)多次+1。
注意點(diǎn)
- 無論是ARC還是MRC中多次調(diào)用
removeFromSuperview和addSubview:方法,都不會(huì)造成造成重復(fù)釋放和添加。 - 蘋果的官方API注明:
Never call this method from inside your view’s drawRect: method.
譯:永遠(yuǎn)不要在你的View的drawRect:方法中調(diào)用removeFromSuperview。