前言:
本篇文章,主要是针对Swift初始化的印象加深。搜集了表达比较好的初始化文章,进行了汇总。
首先我们从初始化方法顺序开始,了解初始化方法有三种,指定、便利、和必要初始化方法。从而针对自定义控件,初始化进行验证,得出自定义控件重写父类初始化方法时,必须提供必要初始化方法,否则会报错。而后又提供了,为什么Swift:必须添加init?(coder decoder: NSCoder)的原因,加深理解。
目录:
001 - 初始化方法顺序
002 - 了解一下指定、便利、必要初始化
003 - Swift:为什么必须添加init?(coder decoder: NSCoder)的原因
初始化方法顺序
与 Objective-C 不同,Swift 的初始化方法需要保证类型的所有属性都被初始化。所以初始化方法的调用顺序就很有讲究。在某个类的子类中,初始化方法里语句的顺序并不是随意的,我们需要保证在当前子类实例的成员初始化完成后才能调用父类的初始化方法:
class Cat { var name: String init() { name = "cat" } } class Tiger: Cat { let power: Int override init() { power = 10 super.init() name = "tiger" } }
一般来说,子类的初始化顺序是:
- 1.设置子类自己需要初始化的参数,power = 10
- 2.调用父类的相应的初始化方法,super.init()
- 3.对父类中的需要改变的成员进行设定,name = "tiger"
其中第三步是根据具体情况决定的,如果我们在子类中不需要对父类的成员做出改变的话,就不存在第 3 步。而在这种情况下,Swift 会自动地对父类的对应 init 方法进行调用,也就是说,第 2 步的 super.init() 也是可以不用写的 (但是实际上还是调用的,只不过是为了简便 Swift 帮我们完成了)。这种情况下的初始化方法看起来就很简单:
class Cat { var name: String init() { name = "cat" } } class Tiger: Cat { let power: Int override init() { power = 10 // 如果我们不需要改变 name 的话, // 虽然我们没有显式地对 super.init() 进行调用 // 不过由于这是初始化的最后了,Swift 替我们自动完成了 } }
了解一下指定、便利、必要初始化
我们在深入初始化方法之前,不妨先再想想 Swift 中的初始化想要达到一种怎样的目的。
其实就是安全。在 Objective-C 中,
init
方法是非常不安全的:没有人能保证init
只被调用一次,也没有人保证在初始化方法调用以后实例的各个变量都完成初始化,甚至如果在初始化里使用属性进行设置的话,还可能会造成各种问题,虽然 Apple 也明确说明了不应该在 init 中使用属性来访问,但是这并不是编译器强制的,因此还是会有很多开发者犯这样的错误。所以 Swift 有了超级严格的初始化方法。一方面,Swift 强化了 designated 初始化方法的地位。Swift 中不加修饰的 init 方法都需要在方法中保证所有非 Optional 的实例变量被赋值初始化,而在子类中也强制 (显式或者隐式地) 调用 super 版本的 designated 初始化,所以无论如何走何种路径,被初始化的对象总是可以完成完整的初始化的。
class ClassA { let numA: Int init(num: Int) { numA = num } } class ClassB: ClassA { let numB: Int override init(num: Int) { numB = num + 1 super.init(num: num) } } 在上面的示例代码中,注意在 init 里我们可以对 let 的实例常量进行赋值,这是初始化方法的重要特点。在 Swift 中 let 声明的值是常量,无法被写入赋值,这对于构建线程安全的 API 十分有用。而因为 Swift 的 init 只可能被调用一次,因此在 init 中我们可以为常量进行赋值,而不会引起任何线程安全的问题。 class ClassA { let numA: Int init(num: Int) { numA = num } convenience init(bigNum: Bool) { self.init(num: bigNum ? 10000 : 1) } } class ClassB: ClassA { let numB: Int override init(num: Int) { numB = num + 1 super.init(num: num) } }
只要在子类中实现重写了父类 convenience 方法所需要的 init 方法的话,我们在子类中就也可以使用父类的 convenience 初始化方法了。比如在上面的代码中,我们在 ClassB 里实现了 init(num: Int) 的重写。这样,即使在 ClassB 中没有 bigNum 版本的 convenience init(bigNum: Bool),我们仍然还是可以用这个方法来完成子类初始化:
let anObj = ClassB(bigNum: true) // anObj.numA = 10000, anObj.numB = 10001
因此进行一下总结,可以看到初始化方法永远遵循以下两个原则:
- 初始化路径必须保证对象完全初始化,这可以通过调用本类型的 designated 初始化方法来得到保证。
- 子类的 designated 初始化方法必须调用父类的 designated 方法,以保证父类也完成初始化。
对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加 required 关键字进行限制,强制子类对这个方法重写实现。这样做的最大的好处是可以保证依赖于某个 designated 初始化方法的 convenience 一直可以被使用。一个现成的例子就是上面的 init(bigNum: Bool):如果我们希望这个初始化方法对于子类一定可用,那么应当将 init(num: Int) 声明为必须,这样我们在子类中调用 init(bigNum: Bool) 时就始终能够找到一条完全初始化的路径了:
class ClassA { let numA: Int required init(num: Int) { numA = num } convenience init(bigNum: Bool) { self.init(num: bigNum ? 10000 : 1) } } class ClassB: ClassA { let numB: Int required init(num: Int) { numB = num + 1 super.init(num: num) } }
另外需要说明的是,其实不仅仅是对 designated 初始化方法,对于 convenience 的初始化方法,我们也可以加上 required 以确保子类对其进行实现。这在要求子类不直接使用父类中的 convenience 初始化方法时会非常有帮助。
自定义控件时,重写父类的初始化,必须提供必要初始化方法,否则会报错
当我们在自定义控件时,我首先想到的是,重写父类的初始化。如下面的代码:
import UIKit class ZBTabBar: UITabBar { // 视图初始化 override init(frame: CGRect) { super.init(frame: frame) } }
但是会报一个错误如下:
错误的意思是:必须提供父类必要初始化的方法required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
由此,我们知道,自定义控件时,重写父类的初始化,必须提供必要初始化方法。
Swift:为什么必须添加init?(coder decoder: NSCoder)的原因
当我们重写类的时候经常提示要添加代码:
required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }
这个叫必要初始化器,这种情况一般会出现在继承了遵守
NSCoding protocol
的类,比如UIView系列的类、UIViewController系列的类。
为什么一定要添加:
这是NSCoding protocol定义的,遵守了NSCoding protoaol的所有类必须继承。只是有的情况会隐式继承,而有的情况下需要显示实现。
什么情况下要显示添加:
当我们使用
storyboard
实现界面的时候,程序会调用这个初始化器。注意要去掉fatalError
,fatalError
的意思是无条件停止执行并打印。在obj-c中可以通过下面代码实现NSException *exception = [NSException exceptionWithName:@"HotTeaException" reason:@"The tea is too hot" userInfo:nil]; @throw exception;
总结:
如果代码实现界面,那么我们只要根据编译器提示添加必要初始化器后,就不用理会,我们创建界面的工作可以在自定义的初始化器里实现。
补充:
let vc = UIViewController()
方式初始化类UIViewController类视乎只有两个初始化器:
- 一个是必要初始化器
init?(coder aDecoder: NSCoder)
- 一个是指定初始化器
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
疑问:
那么为什么我们可以用let vc = UIViewController()
这种方式初始化类呢?
**答: **原因可能是这个初始化方式是来自uikit
,也就是调用了Object-c
下的UIViewController
初始化方法,是object-c bridge
过来的。
学习地址: