- 构造过程是生成类、结构体或枚举实例之前的准备过程。
2. 可以定义构造器来实现构造过程,它就像用来创建特定类型新实例的特殊方法。
3. 构造器的主要任务是保证某种类型的新实例在第一次使用前完成正确的初始化。
12.1 构造器
init() {
// 在此处执行构造过程
}
12.2 存储属性的初始赋值
- 类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
- 可以在定义属性时分配默认值,也可以在构造器中为存储型属性设置初始值。必须确保在
- 为存储型属性分配默认值或者在构造器中设置初始值时,不会触发任何属性观察者。
struct Fahreneit {
var temperature: Double = 10 {
willSet {
print("willSet temperature")
}
didSet {
print("didSet didSet")
}
}
// 这个结构体定义了一个不带参数的init构造器。修改了temperature定义时的值
init() {
print("init")
temperature = 32.0
}
}
// 为存储型属性分配默认值或者在构造器中设置初始值时,不会触发任何属性观察者。
var f = Fahreneit()
print("==========")
f.temperature = 20
12.3 形参的构造过程
struct Color {
// let定义的常量在构造器还有重新赋值的机会。构造器结束后就不可赋值
let red, green, blue: Double
// 通过形参构造Color
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
}
let black = Color(red: 0, green: 0, blue: 0)
12.4 可选属性类型
如果你自定义的类型有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时机可以赋值为空——都需要将它声明为 可选类型。可选类型的属性将自动初始化为 nil,表示这个属性是特意在构造过程设置为空。
class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// 打印“How about beets?”
beetsQuestion.response = "I also like beets. (But not with cheese.)"
12.5 默认构造器
如果结构体或类为所有属性提供了默认值,又没有提供任何自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造器。这个默认构造器将简单地创建一个所有属性值都设置为它们默认值的实例。
12.6 值类型的构造器代理
- 构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能避免多个构造器间的代码重复。
- 构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。类则不同,它可以继承自其它类(请参考 继承)。这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化
- 对于值类型,你可以使用 self.init 在自定义的构造器中引用相同类型中的其它构造器。
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
// 使用 self.init 在自定义的构造器中引用相同类型中的其它构造器
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
12.7 类的继承和构造过程
- 类里面的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。
- Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们被称为指定构造器和便利构造
12.7.1 指定构造器和便利构造器
- 指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并调用合适的父类构造器让构造过程沿着父类链继续往上进行。
- 每一个类都必须至少拥有一个指定构造器。
- 便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为部分形参提供默认值。
12.7.2 指定构造器和便利构造器的语法
// 指定构造器定义语法
init(parameters) {
statements
}
// 便利构造器定义语法
convenience init(parameters) {
statements
}
12.7.3 类类型的构造器代理
为了简化指定构造器和便利构造器之间的调用关系,Swift 构造器之间的代理调用遵循以下三条规则:
- 指定构造器必须调用其直接父类的的指定构造器。
- 便利构造器必须调用同类中定义的其它构造器。
- 便利构造器最后必须调用指定构造器。
一个更方便记忆的方法是:
- 指定构造器必须总是向上代理
- 便利构造器必须总是横向代理
12.7.4 两段式构造过程
Swift 中类的构造过程包含两个阶段。第一个阶段,类中的每个存储型属性赋一个初始值。当每个存储型属性的初始值被赋值后,第二阶段开始,它给每个类一次机会,在新实例准备使用之前进一步自定义它们的存储型属性。
阶段 1
- 类的某个指定构造器或便利构造器被调用。
- 完成类的新实例内存的分配,但此时内存还没有被初始化。
- 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
- 指定构造器切换到父类的构造器,对其存储属性完成相同的任务。
- 这个过程沿着类的继承链一直往上执行,直到到达继承链的最顶部。
- 当到达了继承链最顶部,而且继承链的最后一个类已确保所有的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。
阶段2
- 从继承链顶部往下,继承链中每个类的指定构造器都有机会进一步自定义实例。构造器此时可以访问 self、修改它的属性并调用实例方法等等。
- 最终,继承链中任意的便利构造器有机会自定义实例和使用 self。
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程不出错地完成:
- 指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类的属性在它往上代理之前先完成初始化。
- 指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
- 便利构造器必须为任意属性(包括所有同类中定义的)赋新值之前代理调用其它构造器。如果没这么做,便利构造器赋予的新值将被该类的指定构造器所覆盖。
- 构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用 self 作为一个值。类的实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,类的实例才是有效的,才能访问属性和调用方法。
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
convenience init() {
self.init(name: "unkonw", age: 0)
}
}
安全检查1失败,如下
class Employ: Person {
var salary: Int
init(name: String, age: Int, salary: Int) {
// Property 'self.salary' not initialized at super.init call
// 安全检查1失败:只有本类的实例初始化完成。才能将构造任务代理给父类构造器
super.init(name: name, age: age)
self.salary = salary
self.name = "断肠人在天涯:" + name
}
}
安全检查2失败,如下
class Employ: Person {
var salary: Int
init(name: String, age: Int, salary: Int) {
self.salary = salary
// 'self' used in property access 'name' before 'super.init' call
// 安全检查2: name是继承来的属性,必须先调用父类构造器初始化。要不设置的值根本无效,会被父类覆盖
self.name = "断肠人在天涯:" + name
super.init(name: name, age: age)
}
}
安全检查3失败,如下
class Employ: Person {
var salary: Int
init(name: String, age: Int, salary: Int) {
self.salary = salary
super.init(name: name, age: age)
// 'self' used in property access 'name' before 'super.init' call
// 安全检查2: name是继承来的属性,必须先调用父类构造器初始化。要不设置的值根本无效,会被父类覆盖
self.name = "断肠人在天涯:" + name
}
convenience init(name: String) {
// 'self' used before 'self.init' call or assignment to 'self'
// 安全检查3:便利构造器为任意属性赋值前 都要代理调用其他构造器。如果没这么做,便利构造器赋予的新值将被该类的指定构造器所覆盖。
// self.name 赋值的值会被覆盖
self.name = "断肠人在天涯:" + name
self.init(name: name, age: 18, salary: 300)
}
}
安全检查4失败,如下
class Employ: Person {
var salary: Int
init(name: String, age: Int, salary: Int) {
self.salary = salary
// 'self' used in method call 'test' before 'super.init' call
// 安全检查4:第一阶段完成前类的实例并不是完全有效的
self.test()
super.init(name: name, age: age)
self.name = "断肠人在天涯:" + name
}
func test() {
print("打工仔伤不起")
}
}
12.7.5 构造器的继承和重写
- Swift 中的子类默认情况下不会继承父类的构造器。父类的构造器仅会在安全和适当的某些情况下被继承。
class Vehicle {
var numberOfWheels = 0
var description: String {
return "(numberOfWheels) wheel(s)"
}
}
class Bicycle: Vehicle {
// 构造器重写
override init() {
super.init()
numberOfWheels = 2
}
}
class Hoverboard: Vehicle {
var color: String
init(color: String) {
self.color = color
// super.init() 在这里被隐式调用。因为父类有一个无参数的指定构造器。并且第二阶段啥都不做
}
override var description: String {
return "(super.description) in a beautiful (color)"
}
}
12.7.6 构造器的自动继承
假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则将适用:
- 如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。
- 如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承父类所有的便利构造器。
12.7.8 可失败构造器
有时,定义一个构造器可失败的类,结构体或者枚举是很有用的。这里所指的“失败” 指的是,如给构造器传入无效的形参,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。 为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在 init 关键字后面添加问号(init?)。
class Student {
// 每个学生都要有个学号
var sno: String
// init后面加?表示是一个可失败的构造器
init?(sno: String) {
// 学号如果为空,return nil 表明构造失败
if sno.isEmpty {
return nil
}
self.sno = sno
}
}
print(Student(sno: "")) // nil
无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。
12.7.9 重写一个可失败构造器
你可以用非可失败构造器重写可失败构造器,但反过来却不行。
class SubStudent: Student {
override init(sno: String) {
// A non-failable initializer cannot chain to failable initializer 'init(name:)' written with 'init?'
// 非失败构造器不能调动可失败构造器。要强制解包
super.init(sno: "[unknow]")!
}
}
print(SubStudent(sno: "").sno) // [unknow]
12.7.10 必要构造器
在类的构造器前添加 required 修饰符表明所有该类的子类都必须实现该构造器:
class SomeClass {
required init() {
// 构造器的实现代码
}
}
class SomeSubclass: SomeClass {
required init() {
// 构造器的实现代码
}
}
12.8.1 通过闭包或函数设置属性的默认值
class SomeClass {
let someProperty: SomeType = {
// 在这个闭包中给 someProperty 创建一个默认值
// someValue 必须和 SomeType 类型相同
return someValue
}()
}