探究 UIViewController 生命週期

因爲種種緣由,掘金等第三方平臺博客再也不保證可以同步更新,歡迎移步 GitHub:github.com/kingcos/Per…。謝謝!git

Lifecycle of UIViewController in iOSgithub

Date Notes Swift Xcode
2017-03-10 首次提交 3.0 8.2.1

前言

對象的生命週期一直是開發者所須要關心的,教授 CS193p 的老師 Paul 也詳細的講述了 UIViewController 的生命週期。爲了記述這一過程,故做此文。因爲 Xcode 提供了純代碼和 Storyboard(Xib 同理)兩種佈局 UI 的方式,所以初始化部分略有不一樣。安全

爲了方便觀察,我建立了一個 BaseViewController,繼承自本來的 UIViewController,重寫其中的生命週期方法,並讓後續新的控制器繼承自該控制器,以便觀察。app

本文對應的 Demo 能夠在 github.com/kingcos/UIV… 查看、下載。iview

Structure

Initialization

Storyboard

OUTPUT: init(coder:) awakeFromNib()ide

init(coder:)

  • 當使用 Storyboard 時,控制器的構造器爲 init(coder:)
  • 該構造器爲必需構造器,若是重寫其餘構造器,則必須重寫該構造器。
  • 該構造器爲可失敗構造器,即有可能構造失敗,返回 nil。
  • 該方法來源自 NSCoding 協議,而 UIViewController 聽從這一協議。
  • 該方法被調用意味着控制器有可能(並不是必定)在將來會顯示。
  • 在控制器生命週期中,該方法只會被調用一次。

awakeFromNib()

  • 當使用 Storyboard 時,該方法會被調用。
  • 當調用該方法時,將保證全部的 outlet 和 action 鏈接已經完成。
  • 該方法內部必須調用父類該方法,雖然默認實現爲空,但 UIKit 中許多類的該方法爲非空。
  • 因爲控制器中對象的初始化順序不能肯定,因此構造器中不該該向其餘對象發送消息,而應當在 awakeFromNib() 中安全地發送。
  • 一般使用 awakeFromNib() 能夠進行在設計時沒法完成的必要額外設置。

Code

OUTPUT: init(nibName:bundle:) - NibName: nil, Bundle: niloop

init(nibName:bundle:)

  • 當使用純代碼建立控制器,控制器的構造器爲 init(nibName:bundle:)
  • 雖然使用代碼建立時調用了該構造器,但傳入的參數均爲 nil。

OUTPUT: loadView() viewDidLoad() viewWillAppear viewWillLayoutSubviews() - Optional((162.0, 308.0, 50.0, 50.0)) viewDidLayoutSubviews() - Optional((67.0, 269.0, 241.0, 129.0)) viewDidAppear viewWillDisappear viewDidDisappear deinit佈局

loadView()

  • loadView() 即加載控制器管理的 view。
  • 不能直接手動調用該方法;當 view 被請求卻爲 nil 時,該方法加載並建立 view。
  • 若控制器有關聯的 Nib 文件,該方法會從 Nib 文件中加載 view;若是沒有,則建立空白 UIView 對象。
  • 若是使用 Interface Builder 建立 view,則務必不要重寫該方法。
  • 可使用該方法手動建立視圖,且須要將根視圖分配爲 view;自定義實現不該該再調用父類的該方法。
  • 執行其餘初始化操做,建議放在 viewDidLoad() 中。

viewDidLoad()

  • view 被加載到內存後調用 viewDidLoad()
  • 重寫該方法須要首先調用父類該方法。
  • 該方法中能夠額外初始化控件,例如添加子控件,添加約束。
  • 該方法被調用意味着控制器有可能(並不是必定)在將來會顯示。
  • 在控制器生命週期中,該方法只會被調用一次。

viewWillAppear(_:)

  • 該方法在控制器 view 即將添加到視圖層次時以及展現 view 時全部動畫配置前被調用。
  • 重寫該方法須要首先調用父類該方法。
  • 該方法中能夠進行操做即將顯示的 view,例如改變狀態欄的取向,類型。
  • 該方法被調用意味着控制器將必定會顯示。
  • 在控制器生命週期中,該方法可能會被屢次調用。

注意: 若是控制器 A 被展現在另外一個控制器 B 的 popover 中,那麼控制器 B 不會調用該方法,直到控制器 A 清除。動畫

viewWillLayoutSubviews()

  • 該方法在通知控制器將要佈局 view 的子控件時調用。
  • 每當視圖的 bounds 改變,view 將調整其子控件位置。
  • 該方法可重寫以在 view 佈局子控件前作出改變。
  • 該方法的默認實現爲空。
  • 該方法調用時,AutoLayout 未起做用。
  • 在控制器生命週期中,該方法可能會被屢次調用。

viewDidLayoutSubviews()

  • 該方法在通知控制器已經佈局 view 的子控件時調用。
  • 該方法可重寫以在 view 佈局子控件後作出改變。
  • 該方法的默認實現爲空。
  • 該方法調用時,AutoLayout 已經完成。
  • 在控制器生命週期中,該方法可能會被屢次調用。

viewDidAppear(_:)

  • 該方法在控制器 view 已經添加到視圖層次時被調用。
  • 重寫該方法須要首先調用父類該方法。
  • 該方法可重寫以進行有關正在展現的視圖操做。
  • 在控制器生命週期中,該方法可能會被屢次調用。

viewWillDisappear(_:)

  • 該方法在控制器 view 將要從視圖層次移除時被調用。
  • 相似 viewWillAppear(_:)
  • 該方法可重寫以提交變動,取消視圖第一響應者狀態。

viewDidDisappear(_:)

  • 該方法在控制器 view 已經從視圖層次移除時被調用。
  • 相似 viewDidAppear(_:)
  • 該方法可重寫以清除或隱藏控件。

didReceiveMemoryWarning()

  • 當內存預警時,該方法被調用。
  • 不能直接手動調用該方法。
  • 該方法可重寫以釋放資源、內存。

deinit

  • 控制器銷燬時(離開堆),調用該方法。

Note

Rotation

OUTPUT: willTransition(to:with:) viewWillLayoutSubviews() - Optional((67.5, 269.5, 240.0, 128.0)) viewDidLayoutSubviews() - Optional((213.5, 123.5, 240.0, 128.0)) viewWillLayoutSubviews() - Optional((213.5, 123.5, 240.0, 128.0)) viewDidLayoutSubviews() - Optional((213.5, 123.5, 240.0, 128.0)) viewWillLayoutSubviews() - Optional((213.5, 123.5, 240.0, 128.0)) viewDidLayoutSubviews() - Optional((213.5, 123.5, 240.0, 128.0))ui

  • 當 view 轉變,會調用 willTransition(to:with:) 方法。
  • 當屏幕旋轉,view 的 bounds 改變,其內部的子控件也須要按照約束調整爲新的位置,所以也調用了 viewWillLayoutSubviews()viewDidLayoutSubviews()

Present & Dismiss

OUTPUT: viewWillDisappear viewDidDisappear viewDidDisappear viewWillAppear viewDidAppear

  • 當在一個控制器內 Present 新的控制器,原先的控制器並不會銷燬,但會消失,所以調用了 viewWillDisappearviewDidDisappear 方法。
  • 若是新的控制器 Dismiss,即清除本身,原先的控制器會再一次出現,所以調用了其中的 viewWillAppearviewDidAppear 方法。

死循環

class LoopViewController: UIViewController {

    override func loadView() {
        print(#function)
    }

    override func viewDidLoad() {
        print(#function)
        let _ = view
    }

}
複製代碼

OUTPUT: loadView() viewDidLoad() loadView() viewDidLoad() loadView() viewDidLoad() loadView() viewDidLoad() loadView()

  • loadView() 沒有加載 view,viewDidLoad() 會一直調用 loadView() 加載 view,所以構成了死循環,程序即卡死。

Reference

也歡迎您關注個人微博 @萌面大道V & 簡書

相關文章
相關標籤/搜索