因为自己是直接从 Swift 进入的 iOS 开发,Swift 与 Objective-C 初始化的对比就不多提了。感觉上 Swift 初始化的方式像 Java,自己也只这样套着 Java 去理解,也发现了不相同的地方。

初始化顺序

class Blog: NSObject {
let param: String

override init() {
}
}

这里有条错误 error: property 'self.param' not initialized at implicitly generated super.init call 说明:param 参数没有在隐式生成 super.init 调用之前完成初始化。

Swift 中并不是不调用 super.init 而是为了方便开发者由编译器完成了这一步,但是要求调用 super.init 之前要完成成员变量的初始化。

class Blog: NSObject {
let param: String

override init() {
param = "swift init"
}
}

对于需要修改父类中成员变量值,我们需要在调用 super.init 之后再进行修改:

class Cat {
var name: String

init() {
name = "cat"
}
}

class Tiger: Cat {
let power: Int

override init() {
power = 10
super.init()
name = "tiger"
}
}

Swift 中类的初始化顺序:

  1. 初始化自己的成员变量,必须
  2. 调用父类初始化方法,如无需第三步,则这一步也可省略
  3. 修改父类成员变量,可选

补充说明:

  • let 声明的常量是可以在初始化方法中进行赋值,Swift 中的 init 方法只会被调用一次,这与 Objective-C 不同
  • 即使成员变量是可选类型,如:let power: Int? 仍然是需要进行初始化的,var power: Int? 则可以不用

关键字

例子 1:

class CustomView: UIView {
let param: Int

override init() { // error 1
self.param = 1
super.init() // error 2
}
} // error 3

将父类从 NSObject 修改为 UIView,竟然收到 3 条错误:

  1. initializer does not override a designated initializer from its superclass
  2. must call a designated initializer of the superclass ‘UIView’
  3. ‘required’ initializer ‘init(coder:)’ must be provided by subclass of ‘UIView’

例子 2:

class CustomView: UIView {
convenience init(param: Int, frame: CGRect) {
super.init(frame: frame) // error
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

error:

  • convenience initializer for ‘CustomView’ must delegate (with ‘self.init’) rather than chaining to a superclass

上面俩个例子出现了 3 个关键字:designatedconveniencerequired

designated

Swift 定义了两种类初始化器类型,用来保证所有成员属性能够获得一个初始化值。即 designated initializers [i’niʃəlaizə] 和 convenience initializers。

Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.

  1. primary initializers:designated initializers 是一个类的主初始化器,理论上来说是一个类初始化的必经之路。(不同的初始化路径可能调用不同的 designated initializers)
  2. fully initializes all properties:必须在 designated initializers 中完成所有成员属性的初始化
  3. calls an appropriate superclass initializer:需要调用合适的父类初始化器完成初始化,不能随意调用

在 Swift 中 designated initializers 的写法和一般的初始化方法无异,Sample 1 中,我们试图去 override init,可以理解为我们就是在 override 一个 designated initializers,然后我们收到了错误 Initializer does not override a designated initializer from its superclass,可见我们并没有找到合适的 designated initializers,我们进入父类 UIView,可以看到下面两个初始化方法:

public init(frame: CGRect)
public init?(coder aDecoder: NSCoder)

原来,这两个类才是父类的 designated initializers,那我们改改试试:

class CustomView: UIView {
let param: Int

override init(frame: CGRect) { // error 1 fixed
self.param = 1
super.init(frame: frame) // error 2 fixed
}
} // error 3

error 1 fixed 由此可见:我们去 override 一个不是 designated initializers 的初始化器时,是不满足定义中所说的 primary initializers,这就可能导致这个初始化器不被执行,成员变量没有初始化,这样创建的“半成品”实例可能存在一些不安全的情况。

第二条 fully initializes all properties,这点我们并没有犯错,因为我们已经初始化了 CustomView 类中引入的 param 变量。

第三条 calls an appropriate superclass initializer 很明显就对应了 error 2,我们 override init(frame:),那我们就必须调用对应的父类初始化方法。

error 3 提示我们 init(coder:) 是一个 ‘required’ initializer,子类必须提供

required

Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer.

通过添加 required 关键字强制子类对某个初始化方法进行重写。

class CustomView: UIView {
let param: Int

override init(frame: CGRect) { // error 1 fixed
self.param = 1
super.init(frame: frame) // error 2 fixed
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
} // error 3 fixed

error 3 fixed 插入的这个方法很奇怪,方法体里直接写 fatalError("init(coder:) has not been implemented"),那岂不是走到这里就 crash 了?

designated initializers 是一个类的主初始化器,理论上来说是一个类初始化的必经之路(不同的初始化路径可能调用不同的 designated initializers),其实,这个 init(coder:)init(frame: frame) 就是不同的初始化路径,当我们使用 xib 方式初始化一个 view 时,就会走到 init(coder:)。此时,如果我们没有真正实现这个方法,就会出现 fatal crash。

完整初始方案:

class CustomView: UIView {
let param: Int

override init(frame: CGRect) {
self.param = 1
super.init(frame: frame)
}

required init?(coder aDecoder: NSCoder) {
self.param = 1
super.init(coder: aDecoder)
}
}

convenience

Convenience initializers are secondary, supporting initializers for a class. You can define a convenience initializer to call a designated initializer from the same class as the convenience initializer with some of the designated initializer’s parameters set to default values. You can also define a convenience initializer to create an instance of that class for a specific use case or input value type.

convenience initializers 是对类初始化方法的补充,用于为类提供一些快捷的初始化方法,可以不创建这类方法,但如果创建了,就需要遵循原则:call a designated initializer from the same class,也就是说要调用该类自己的 designated initializer,那么我们应该 override init(frame:) ,然后修改为:

class CustomView: UIView {
convenience init(param: Int, frame: CGRect) {
self.init(frame: frame) // error fixed
}

override init(frame: CGRect) {
super.init(frame: frame)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

然后对成员变量 param 赋值:

class CustomView: UIView {
var param: Int

convenience init(param: Int, frame: CGRect) {
self.param = param // error
self.init(frame: frame)
}

override init(frame: CGRect) {
super.init(frame: frame) // error
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

来个两个错误:

  • Use of ‘self’ in property access ‘param’ before self.init initializes self
  • Property ‘self.param’ not initialized at super.init call

第二个错误我们清楚,是需要在调用 super.init 之前初始化本类成员属性。

第一个错误其实,这是 Swift 编译器提供的安全检查,文档原文如下:

A convenience initializer must delegate to another initializer before assigning a value to any property (including properties defined by the same class). If it doesn’t, the new value the convenience initializer assigns will be overwritten by its own class’s designated initializer.

原来 Swift 防止 convenience initializers 中赋值之后又被该类自己的 designated initializer 覆盖而做了检查。因此,正确的方式应该是调用该类的其他初始化方法之后再修改属性值,最终修改如下:

class CustomView: UIView {
var param: Int

convenience init(param: Int, frame: CGRect) {
self.init(frame: frame)
self.param = param // error fixed
}

override init(frame: CGRect) {
self.param = 0 // error fixed
super.init(frame: frame)
}

required init?(coder aDecoder: NSCoder) {
self.param = 0
super.init(coder: aDecoder)
}
}

小结

  1. 子类中初始化方法必须覆盖全部初始化路径,以保证对象完全初始化
  2. 子类中 designated initializer 必须调用父类中对应的 designated initializer,以保证父类也能完成初始化
  3. 子类中如果重写父类中 convenience initializer 所需要的全部 init 方法,就可以在子类中使用父类的 convenience initializer
  4. 子类如果没有定义任何 designated initializer,则默认继承所有父类的 designated initializerconvenience initializer
  5. 子类中必须实现的 designated initializer,可以通过添加 required 关键字强制子类重写其实现,以保证依赖该方法的 convenience initializer 始终可以使用
  6. convenience initializer 必须调用自身类中的其他初始化方法,并在最终必须调用一个 designated initializer
  7. 在构造器完成初始化之前, 不能调用任何实例方法,或读取任何实例属性的值,self 本身也不能被引用

可失败初始化器

可失败初始化器 Failable Initializers ,即可能返回 nil 的初始化方法。

A failable initializer creates an optional value of the type it initializes. You write return nil within a failable initializer to indicate a point at which initialization failure can be triggered.

就是将初始化返回值变成 optional value,并在不满足初始化条件的地方 return nil。通过调用处判断是否有值即可知道是否初始化成功。

class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}

class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 { return nil }
self.quantity = quantity
super.init(name: name)
}
}

CartItem 类的初始化方法先对传入参数 quantity 的值进行判断,小于 1 则为无效参数,然后 return nil(初始化失败),大于或等于 1 则继续调用父类 Product 的初始化方法,再次判断传入参数 name,为空则 return nil(初始化失败),否则继续初始化。

总的来说,可失败初始化器的设定,是在保证安全性的基础上提供了逻辑上更清晰的初始化方式。Failable Initializers 所有的结果都将是 T? 类型,通过 Optional Binding 方式,我们就能知道初始化是否成功,并安全地使用它们了。

Reference:

– EOF –