初探 iOS 中自定义 UIView 的初始化过程

由于种种原因,简书等第三方平台博客不再保证能够同步更新,欢迎移步 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。

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 的载入过程如下:

  1. Nib 文件内容和引用的资源文件加载到内存;
  2. 反归档存储于 Nib 文件的图像数据对象并初始化;
  3. 遵从 NSCoding 的对象(UIView & UIViewController)调用 init(coder:)
  4. 其他对象调用其他构造器方法
  5. 建立对象间连接:Outlet & Action
  6. 实现 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!

参考资料

Nib Files
Initialization

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 148,637评论 1 318
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 63,443评论 1 266
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 99,164评论 0 218
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,075评论 0 188
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,080评论 1 266
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,365评论 1 184
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,901评论 2 283
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,649评论 0 176
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,122评论 0 223
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,734评论 2 225
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,093评论 1 236
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,548评论 2 222
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,028评论 3 216
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,765评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,291评论 0 178
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,162评论 2 239
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,293评论 2 242

推荐阅读更多精彩内容