由于種種原因,簡書等第三方平臺博客不再保證能夠同步更新,歡迎移步 GitHub:https://github.com/kingcos/Perspective/。謝謝!
awakeFromNib()
init(frame:)
init(coder:)
- Info:
- macOS 10.12.1
- Xcode 8.2 Beta 1
- Swift 3.0
Update
2017.03.07 - UIView 生命周期 Demo
由于本文之前雖有代碼,但沒有相應的 Demo,借探究 UIViewController 生命周期之際,加入了 UIView 生命周期的 Demo。您可以在 https://github.com/kingcos/UIViewController-UIView-LifecycleDemo 查看、下載。
2017.02.27 - CS193p Lecture 04
- 通常,UIView 應盡可能避免重寫構造器。
-
init(frame:)
:純代碼(指定構造器);init(coder:)
:Storyboard(必需可失敗構造器)。若需要構造器,需要同時重寫這兩個構造器:
func setup() {
// 如果本類有自定義變量,則此處不可初始化他們,
// 因為只有當初始化后才能調用自己的方法。
}
override init(frame: CGRect) {
super.init(frame: frame)
// 初始化變量
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// 初始化變量
setup()
}
-
awakeFromNib()
只在使用 Storyboard 的 UIView 中被調用。 -
awakeFromNib()
并不是構造器,但它在初始化完成后立即被調用。 - 所有 Storyboard 中繼承自 NSObject 的對象發送該消息。但順序是不確定的,因此不能在這里調用其他任何 Storyboard 中的對象。
前言
在 StoryBoard 和 Xib 出現之后,iOS UI 開發出現了三足鼎立之勢。本文不涉及 StoryBoard、Xib、純代碼的優劣之分。僅僅涉及幾個初始化方法:awakeFromNib()
& init(frame:)
& init(coder:)
,探討他們何時調用,為何調用。
Xib & Nib
ib 是 Interface Builder 的縮寫,即界面構造器。這里簡要說下,Xib 和 Nib 各是什么,有什么區別。
Xib 實際是一個 XML 文件,而 Nib 是二進制文件。當應用編譯時,Xib 文件被翻譯為 Nib。所以在 Xcode 中,我們可以自己新建 Xib 文件來構造 UI,而當編譯時,Xcode 會自動生成相應的 Nib 文件,而不需我們額外關注。關于其詳細介紹,您可以參考文末的資料。
OK! Talk is cheap, show me the code!
Demo
在下面的 Demo 中,統一將自定義 UIView 命名為 MyView。
MyView.swift
import UIKit
class MyView: UIView {
// methods
}
Interface Builder
如果使用 Interface Builder 拖控件,那么其默認屬于 UIView 類型。為將其改為自定義控件,需要將 Utilities 中 Identity inspector 的 Custom Class 改為 MyView。
為了方便看出調用順序,將 MyView.swift 改為如下:
import UIKit
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
print("init(frame:)")
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
print("init(coder:)")
// fatalError("init(coder:) has not been implemented")
}
override func awakeFromNib() {
super.awakeFromNib()
print("awakeFromNib()")
}
}
之后運行即可在屏幕上看到該自定義 UIView,控制臺輸出:
init(coder:)
awakeFromNib()
小結
通過打印的輸出,可以看出使用 Interface Builder 載入 View 不會調用 init(frame:)
方法,而是調用了 init(coder:)
。init(coder:)
是 NSCoding 協議中的方法,NSCoding 是負責編碼解碼,歸檔處理的協議。
required init?(coder aDecoder: NSCoder)
代碼中的 init(coder:)
與平時見到的其他初始化方法有點不同:required
是指其為必要構造器,即子類「必須」重寫該構造器,但當父類的構造器可以完全滿足初始化時,也可不重寫。init?
是指其為可失敗構造器,即其可以 return nil
告知外界構造失敗。若想詳細了解 Swift 中的構造器,可以參考蘋果官方文檔。
init(coder:)
的調用處于 Nib 載入時,而 awakeFromNib()
的調用處于 Nib 載入后。Nib 的載入過程如下:
- Nib 文件內容和引用的資源文件加載到內存;
- 反歸檔存儲于 Nib 文件的圖像數據對象并初始化;
- 遵從 NSCoding 的對象(UIView & UIViewController)調用
init(coder:)
- 其他對象調用其他構造器方法
- 建立對象間連接:Outlet & Action
- 實現
awakeFromNib()
的對象調用該方法
需要注意的是,awakeFromNib()
中需要調用父類的該方法以保證父類的進行額外初始化。而在本例中重寫的 init(coder:)
目的主要是查看調用順序,并沒有加入特別的操作。因此在實際使用中,如果使用 Interface Builder,可以不重寫該方法。
Code
MyView.swift
import UIKit
class MyView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
print("init(frame:)")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func awakeFromNib() {
super.awakeFromNib()
print("awakeFromNib")
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let myView = MyView(frame: CGRect(x: 100, y: 100, width: 50, height: 50))
myView.backgroundColor = .black
view.addSubview(myView)
}
}
之后運行即可在屏幕上看到該自定義 UIView,控制臺輸出:
init(frame:)
小結
通過純代碼創建自定義 UIView,便只調用 init(frame:)
方法,不涉及 Nib 的方法,因此不會調用 awakeFromNib()
和 init(coder:)
方法。而由于 init(coder:)
為必要構造器,因此重寫 init(frame:)
時,必須實現該方法。
有時,為了便于從 Interface Builder 和純代碼都能創建自定義 UIView 對象,可以將 init(coder:)
方法改為:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// fatalError("init(coder:) has not been implemented")
}
若保留 fatalError()
,則從 Nib 初始化時會無條件輸出語句并停止運行。
后記
可能是強迫癥作祟,學習中每遇到一個知識點,都想要查看官方文檔或者 Google 出為什么,然后自己敲代碼驗證,再總結出一篇文章,投稿給簡書、掘金。一篇文章有時要耗費一兩天,因為查閱的資料都是略有過時且幾乎全為英文,但自己挺享受這樣的學習狀態,也很享受分享給大家之后獲得的收藏所帶來的鼓勵。最近也看了很多實習生的招聘,現在深感基礎的重要,未來可能會傾向一些基礎,例如數據結構、算法、網絡等知識。也希望自己在寒假或下學期能找一份 iOS 實習,雖然自己也有所涉獵 Android 等其他的一些技術棧,但還是對 iOS 最感興趣。Come on!