1. 起因
我們經常能夠在第三方庫的源碼中看到很多loadView
、willMoveToParentViewController:
、viewDidLayoutSubviews
等等諸如此類的并不是十分常見的方法。這讓永遠都只在viewDidLoad
寫作的童鞋們情何以堪吶。
這些其實都和生命周期有關,和viewController以及view的各種加載順序有關。這篇文章就小小擼一下這中間的關系和順序。
2. Controller的生命周期
- 系統提供了控制器從顯示到消失的四個方法。
- 千萬不要看到方法名中間出現了
view
就以為這是視圖的方法。這些都是控制器的生命周期。
四個方法如下:
//將要顯示,一定要調用super
- (void)viewWillAppear:(BOOL)animated; // Called when the view is about to made visible. Default does nothing
//視圖已經顯示
- (void)viewDidAppear:(BOOL)animated; // Called when the view has been fully transitioned onto the screen. Default does nothing
//視圖將要消失
- (void)viewWillDisappear:(BOOL)animated; // Called when the view is dismissed, covered or otherwise hidden. Default does nothing
//視圖已經消失
- (void)viewDidDisappear:(BOOL)animated; // Called after the view was dismissed, covered or otherwise hidden. Default does nothing
那四個階段都有什么區別呢?啥時候用什么呢?
2.1 viewWillAppear
當view即將被顯示時調用,此時superview 為nil,也就是說這個是controller還不知道superview是誰。
這個階段會加載一些高開銷的操作,例如:鍵盤彈出、特殊的過程動畫(比方說修改狀態欄、導航條顏色等等)這些。
2.2 viewDidAppear
這個方法表面上看上和viewDidLoad沒有什么區別啊。
但是請注意一下細節。官方是這么描述viewDidLoad
。
Called after the view has been loaded,For view controllers unarchived from a nib, this is after the view is set.
什么意思?意思是說viewDidLoad
在視圖被加載后調用。如果使用了布局文件,那么會在布局文件加載后被調用。
我們再來看看viewDidAppear
的描述:
Called when the view has been fully transitioned onto the screen
意思是視圖出現在屏幕上之后才調用。
為了能夠進一步搞清楚之間的區別,我們在不同屏幕上運營一下這兩個方法看看調度的時間點。我們將Xcode默認使用5S去設置一下,屏幕大小是320*568,但是如果運行在6s上會怎么樣。下面做一個小測試,打印在不同方法執行的過程中,界面的長寬屬性如何,結果如下:
//viewDidLoad w:320.000000 h:568.000000
//viewWillAppear w:320.000000 h:568.000000
//viewDidAppear w:414.000000 h:672.000000
有沒有發現在viewWillAppear
、viewDidAppear
獲取的長寬不一致??所以系統在這兩者之間肯定存在一個屏幕適配的過程。
- 從上面的結果可以看到,如果需要調整空間的frame,其實是放在
viewDidAppear
中最靠譜的。 - 在自定義cell的時候也有這種問題,如果在init中添加控件的話,那么self.frame.size.width也不一定是準確的寬度,一般解決方法是使用[UIScreen mainScreen].bounds.size.width而不是self.frame.size.width。
2.3 viewWillDisappear
Called when the view is dismissed, covered or otherwise hidden. Default does nothing
視圖被駁回時調用,覆蓋或以其他方式隱藏。默認情況下不執行任何操作。
貌似看上去這個方法沒啥用處,但是隨著iPhone手機屏幕越來越大,左上角的返回按鈕早已夠不到(很多妹子的手都沒有那么大哦!),所以小手的吃瓜群眾通常喜歡通過側滑的形式返回上一個界面。
而這種側滑返回會出現什么問題呢?當觸發側滑返回時會調用系統自帶的viewWillDisappear:方法。
- iOS7新增加了導航控制器側滑手勢,當觸發側滑返回時,會調用系統的
viewWillDisappear:
方法,取消側滑返回時又會調用viewWillAppear:
方法。
2.4 viewDidDisappear
Called after the view was dismissed, covered or otherwise hidden. Default does nothing
對象的視圖已經消失、被覆蓋或是隱藏時調用.
UIViewController
類提供一些方法,用來判斷為什么view外觀發生更改。
- (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0);
- (BOOL)isBeingDismissed NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingToParentViewController NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingFromParentViewController NS_AVAILABLE_IOS(5_0);
在四個方法中:
isMovingFromParentViewController 會在viewWillDisappear
& viewDidDisappear
方法內部調用這個方法判斷視圖控制器的視圖的隱藏是否因為視圖控制器從它的容器視圖控制器移除。
isBeingDismissed 會在viewWillDisappear
& viewDidDisappear
方法內部調用這個方法判斷視圖控制器的視圖的隱藏是否因為視圖控制器被清退 (dismissed,與上面被其它視圖控制器顯示對應,如信息錄入完成,返回之前的視圖控制器)。
2.5 控制器View的生命周期
-
loadView
:加載view- 作用:用來創建控制器的View。在執行的時候會首先判斷有沒有指定的storyboard或者Xib,如果指定,就會加載它們描述的控制器的View,如果沒有指定,創建一個空的View。
- 調用時刻:每次訪問Controller的View,當View為nil,就會調用loadView方法。
-
ViewDidLoad
:view加載完畢- 當控制器的loadView方法執行完畢,view被創建成功后,就會執行viewDidLoad方法。
ViewWillAppear
:view將要顯示ViewWillLayoutSubViews
:view將要布局子控件ViewDidLayoutSubViews
:view布局子控件完成ViewDidAppear
:view完全顯示ViewWillDisAppear
:view即將消失ViewDidDisAppear
:view完全消失
3. View的生命周期
我們知道view的創建有init
(或new
或者跟類名一樣的)的方法,銷毀時會自動執行dealloc
方法,但是UIView的生命周期到底是怎樣的呢?系統也同樣提供了四個方法用來管理四個不同時期的內容,有一點需要注意的是這四個方法都會執行,只是添加或者移除的父視圖不同。
//將要添加到父視圖上,要執行addSubview
- (void)willMoveToSuperview:(nullable UIView *)newSuperview;
//已經添加到父視圖上
- (void)didMoveToSuperview;
//將要添加到窗口
- (void)willMoveToWindow:(nullable UIWindow *)newWindow;
//已經添加到窗口
- (void)didMoveToWindow;
- 某個視圖的層次一改變,該視圖就會收到一次回調。
- 調用addSubivew:成功后會給該視圖發送didAddSubivew:回調,觸發UIView的子類在新增視圖時執行其他操作。
- didMoveToSuperview:會通知相關視圖他們的上級視圖已經變化。添加和移除都會調用,所以要判斷 superView在不在。
- 視圖移動前會發出willMoveToSuperview:回調
- didMoveToWindow:回調和didMoveToSuperview:相似,從命名上能看出其區別。
- willMoveToWindow:在視圖移動前發出的回調。
- willRemoveToSubview:回調通知父視圖子視圖即將被刪除
4. 內存警告
- 首先要判斷一下,當前view有沒有被顯示。如果正在顯示,做處理,會讓用戶感覺很不舒服。
- 更嚴謹一點,還需要判斷view是否已經加載。如果沒有加載,就不需要干掉了。
- 實際開發中為了寫的少點,都會寫在基類控制器中。
- 官方說,iOS 6.0以后系統就不會自動清理,需要手動清理。
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// 第一個判斷條件:當這個view是否正在顯示
// 第二個判斷條件:這個view是否已經被加載
if (self.isViewLoaded && self.view.window == nil) {
[self setView:nil];
}
·