本文轉自:劉小壯(@Eren_liu),轉載請注明原文出處
之前寫過一篇關於removeFromSuperview方法處理的文章,寫完後一直就沒怎麼更新這篇文章。這兩天回過頭來看看,感覺這篇文章有些地方寫的不夠嚴謹,而且還有一些自己理解錯的地方,所以打算重寫這篇文章。
在使用removeFromSuperview方法的時候,發現這個方法有很多我們沒有注意的地方。而且對於一些不規范的操作,蘋果也對其進行了容錯處理。所以我對removeFromSuperview的一些使用細節整理了一下,包括ARC和MRC兩種情況。其中還會簡單涉及一些內存管理相關的部分,文章中有什麼問題,還希望多多指出,謝謝!
占位圖
測試環境
視圖結構
在iOS應用中,視圖的結構是樹型數據結構,以這種結構來控制視圖顯示,這種數據結構有一個很好的優點:
層級關系分明,並且方便傳遞事件。從根節點出發,通過葉節點向下擴展,同一枝的上一個節點就是下一個節點的superview,下一個節點就是上一個節點的subview。每個應用程序有一個主Window,這個Window就是根節點。
removeFromSuperview
每一個View都和視圖結構和響應者鏈有直接的關系,但是這篇文章不打算著重的講這兩個方面,主要講removeFromSuperview方法。將當前視圖從其父視圖移除,需要調用removeFromSuperview方法。下面是蘋果對於這個API的官方定義:
Unlinks the receiver from its superview and its window, and removes it from the responder chain.
譯:把當前View從它的父View和窗口中移除,同時也把它從響應事件操作的響應者鏈中移除。
removeFromSuperview就是一個視圖節點刪除的操作,執行這個方法,就等於在樹形結構中找到該節點,從樹型數據結構中刪除該節點及其子節點,而並非只是刪除該節點自己。同時,另一個操作就是把該對象從響應者鏈中移除。
執行removeFromSuperview方法,只是該視圖不在屏幕中顯示,並沒有將該視圖從內存中移除。所以我們如果需要使用該視圖,不需要再次創建,而是直接addSubview就可以了。
對於這個API,蘋果並沒有給出過多的解釋,只是簡單的描述了一下這個API,以及說明了這個API的注意點。所以,下面將會根據我的使用經驗,繼續講解這個API。
內存管理
方法調用後的內存管理
經過測試,在ARC的情況下執行removeFromSuperview方法多次也沒有問題,因為ARC內存是系統為我們管理的。
但是在MRC中,根據官方API的說明:
If the view’s superview is not nil, the superview releases the view.
也就是每執行一次removeFromSuperview方法,方法內部都會執行一次release操作。但是經過我的測試,發現調用removeFromSuperview方法後,引用計數並沒有減少,反而增加了一個。(我是通過調用retainCount查看的引用計數,但是並不是真正准確的,後面會講解這個問題)
內存陷阱
那如果是這樣,那就遇到一個和我們之前認知不太相同的答案了。具體是什麼問題,還是需要我們自己寫代碼驗證,於是我基於上面描述的測試環境,寫了一些關於視圖的測試代碼。
UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds]; [self.view addSubview:view]; [view release]; [view removeFromSuperview]; // 多次調用remove方法 [view removeFromSuperview];
經過我的測試發現,調用removeFromSuperview方法後引用計數並沒有增加,調用完之後還是會release的。我們之前看到的引用計數的增加,是因為系統的隱藏操作導致的。之前在MRC時期經常發現retainCount不准確,這主要是因為iOS系統API的引用、或自動釋放池導致的,所以retainCount並不能當做可靠的參考。
所以,如果調用多個release,還是會崩潰的,始終要相信iOS的MRC內存管理原則,這才是可靠的。可以多次調用removeFromSuperview方法,在已經移除父視圖後,其他多余的調用不會改變任何引用計數。對於addSubview:方法也是一樣的,下面會講這個方法。
使用細節
多次執行addSubview:操作
假設現在有ViewA、ViewB、ViewC三個視圖,ViewA添加到ViewB之後又要添加到ViewC上面,此時ViewA同時執行了向ViewB、ViewC兩個視圖addSubview:的操作。但是因為只有一個視圖對象,所以只會以最後一次添加的為准,第一次執行的添加到ViewB的操作是無效的。通過打印兩個View的子視圖可以看到,只有最後執行的添加到ViewC上的操作才是有效的,ViewC才真正擁有了ViewA,而ViewB的子視圖是空的。
一個視圖不只是向其他多個頁面進行添加操作不會出現問題,而且向同一個視圖上執行多次添加操作也是沒有問題的,並不會導致視圖被多次添加的問題,也不需要在添加之前進行removeFromSuperview操作,這個是在MRC和ARC都是有效的。因為系統在addSubview:方法中進行了一些判斷操作,如果當前視圖已經添加到其他視圖,會將當前視圖從其他視圖中移除,然後執行添加操作。如果當前視圖已經添加到這個視圖中,就不會再次執行添加操作。
一個小坑
其實也說不上是坑,可以算是一個了解的知識點吧。在ARC或MRC的情況下,調用removeFromSuperview和addSubview:方法其中之一,都需要在另一個方法已經執行的情況下才會有效,對於多次執行一個同方法系統也是有判斷操作的,並不會被執行多次。
例如調用remove方法之後,此時視圖已經不在父視圖之上了,在多次調用這個方法是不起作用的,而且MRC下引用計數也不會被減少多次。對於addSubview:方法也是一樣的,向同一個父視圖上添加子視圖,不會被重復添加,添加之後引用計數也不會多次+1。
注意點
無論是ARC還是MRC中多次調用removeFromSuperview和addSubview:方法,都不會造成造成重復釋放和添加。
蘋果的官方API注明:Never call this method from inside your view’s drawRect: method.
譯:永遠不要在你的View的drawRect:方法中調用removeFromSuperview。