构造器,又叫初始化方法,想必你们都了解。不管是和 class 仍是 struct 打交道,都逃不了初始化这一步骤。不过最近在回看 Swift 文档的时候,我发现了☝️以前未曾注意到的细节。html
class ClassA {
let name: String
init(name: String) {
self.name = name
}
}
class ClassB: ClassA {
let age: Int
init(name: String, age: Int) {
super.init(name: name)
self.age = age
}
}
复制代码
咱们在写子类的初始化方法时,势必须要在其中调用父类的初始化方法。而上面的初始化方法,其实会报错,而报错内容是:objective-c
Property 'self.age' not initialized at super.init call
swift
这个错误提示很简单,子类的属性必需要在父类的初始化方法调用以前初始化,或者说白一点,self.age = age
放到 super.init(name: name)
以前就行了。可是,你们有想过,为何要这样吗?这条规则背后的设计逻辑是什么?安全
不少人首先会联想到 Objective-C 的构造方法ide
(instancetype)init
{
self = [super init];
if (self != nil) {
self.name = @"Jack";
}
return self;
}
复制代码
很明显,Objective-C 的初始化方法无一例外都首先调用了父类的构造方法,而后再初始化子类的属性,同时这也是符合开发人员直觉的。那为啥 Swift 恰恰不行呢?难道是 Swift 的特有机制致使的?flex
让咱们看看 Swift 官方文档 是怎么说的。Swift 中的类初始化是一个分为两个阶段的过程。 这其中,编译器会执行四个有用的安全检查,以确保构造器完成时没有错误。这其中的第一条规则就是:ui
A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.spa
指定的初始化程序必须确保其类引入的全部属性在委托给超类初始化程序以前都已初始化 。设计
而对于第一条规则的解释,虽然没有明确写在规则以后,但 Apple对于 两个阶段
四个安全检查
的最终目的是很明确的:code
The use of a two-phase initialization process makes initialization safe, while still giving complete flexibility to each class in a class hierarchy. Two-phase initialization prevents property values from being accessed before they are initialized, and prevents property values from being set to a different value by another initializer unexpectedly.
两阶段初始化过程的使用使初始化安全,同时仍为类层次结构中的每一个类提供了彻底的灵活性。两阶段初始化可防止在初始化属性值以前对其进行访问,并防止其余初始化程序意外地将属性值设置为其余值。
这个解释彷佛有些抽象,后来看到了一个博客举的一个例子,才恍然大悟。
class ClassA {
let name: String
init(name: String) {
self.name = name
description()
}
func description() {
print("我已经初始化好啦,个人名字是: \(name)")
}
}
class ClassB: ClassA {
let age: Int
init(name: String, age: Int) {
self.age = age
super.init(name: name)
}
override func description() {
print("我已经初始化好啦,个人名字是: \(name), 个人年龄是: \(age)")
}
}
复制代码
思考一下,子类属性的初始化和父类的初始化方法的调用顺序,真的没有任何影响吗?假如咱们先初始化父类的构造方法,而后再去给 age 赋值,结果会怎么样?
很明显,最终程序将没法运行,由于此时父类调用子类的 description
方法中, age
属性还还没有初始化。注意,因为 age
使用了 let
关键词修饰,所以其属性必需要在构造器中初始化。在构造器初始化 age
属性以前, age
的值都没法访问。没法访问的意思不是指 age
的值为 nil, 而是没有被初始化,即内存还没有被分配。这一点和 Objective-C 不一样,由于在 Objective-C 中,属性声明后便有了初始值 nil ,所以即使在构造器中未对其初始化,也能够访问属性的值(nil)。对于这一点,Swift 文档也在其中进行了说明。
Swift’s two-phase initialization process is similar to initialization in Objective-C. The main difference is that during phase 1, Objective-C assigns zero or null values (such as 0 or nil) to every property. Swift’s initialization flow is more flexible in that it lets you set custom initial values, and can cope with types for which 0 or nil is not a valid default value.
Swift 的两阶段初始化过程相似于 Objective-C 中的初始化。主要区别在于,在阶段 1 中,Objective-C 为每一个属性分配零或空值(例如 0 或 nil )。Swift 的初始化流程更加灵活,由于它可让您设置自定义初始值,而且能够处理有效值 0 或 nil 无效值的类型。
正由于 Swift 的构造器更加灵活(容许不可变属性在构造器中初始化),也更加安全(属性须要在初始化后才能访问),所以也不难理解 Apple 对 Swift 的构造器有额外的要求。