学习Swift -- 构造器(中)

时间:2023-03-09 03:41:38
学习Swift -- 构造器(中)

构造器(中)

值类型的构造器代理

构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。

构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。类则不同,它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。这些责任将在后续章节类的继承和构造过程中介绍。

对于值类型,你可以使用self.init在自定义的构造器中引用其它的属于相同值类型的构造器。并且你只能在构造器内部调用self.init

如果你为某个值类型定义了一个定制的构造器,你将无法访问到默认构造器(如果是结构体,则无法访问逐一对象构造器)。这个限制可以防止你在为值类型定义了一个更复杂的,完成了重要准备构造器之后,别人还是错误的使用了那个自动生成的构造器。

注意:假如你想通过默认构造器、逐一对象构造器以及你自己定制的构造器为值类型创建实例,我们建议你将自己定制的构造器写到扩展(extension)中,而不是跟值类型定义混在一起。

struct Size {
var width = 0.0
var height = 0.0
} struct Point {
var x = 0.0
var y = 0.0
} struct Rect {
var size = Size()
var origin = Point() init() {} init(origin: Point, size: Size){
self.size = size
self.origin = origin
} init(center: Point, size: Size){
let x = center.x - size.width / 2
let y = center.y - size.height / 2
self.init(origin:Point(x: x, y: y), size:size)
} init(x: Double, y: Double, width: Double, height: Double){
self.init(origin: Point(x: x, y: y), size: Size(width: width, height: height))
}
} let basicRect = Rect()
let originRect = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 100.0, height: 100.0))
let centerRect = Rect(center: Point(x: 50.0, y: 50.0), size: Size(width: 100.0, height: 100.0))
let customRect = Rect(x: 25.0, y: 25.0, width: 50.0, height: 50.0)
// 第三和第四个构造方式内部都是在构造内部代理给“init(origin: Point, size: Size)” 构造器来提供属性的值

指定构造器和便利构造器

类里面的所有存储型属性--包括所有继承自父类的属性--都必须在构造过程中设置初始值。

Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。

指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。

每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。

便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。

你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。

class SomeClass {
var name = ""
var discription: String? init(name: String) {
// 这是自定构造器的写法
self.name = name
} convenience init(discription: String) {
// 这是便利构造器的写法
self.init(name: discription)
self.discription = discription
}
}

类的构造器的代理规则

Swift以三条规则来限制构造器之间的代理调用:

  • 指定构造器必须调用其直接父类的的指定构造器。
  • 便利构造器必须调用同一类中定义的其它构造器。
  • 便利构造器必须最终以调用一个指定构造器结束。

学习Swift -- 构造器(中)

两段式构造过程

Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性通过引入它们的类的构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。

两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器意外地赋予不同的值。

Swift还会执行4中安全检查,以确保两段式构造过程能够顺利完成

  1. 指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。

    如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化

  2. 指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
  3. 便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
  4. 构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值,self的值不能被引用。

    类实例在第一阶段结束以前并不是完全有效,仅能访问属性和调用方法,一旦完成第一阶段,该实例才会声明为有效实例。

以下是两段式构造过程中基于上述安全检查的构造流程展示:

阶段1:

  • 某个指定构造器或便利构造器被调用;
  • 完成新实例内存的分配,但此时内存还没有被初始化;
  • 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化;
  • 指定构造器将调用父类的构造器,完成父类属性的初始化;
  • 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;
  • 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段1完成。

学习Swift -- 构造器(中)

阶段2:

  • 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。
  • 最终,任意构造器链中的便利构造器可以有机会定制实例和使用self

学习Swift -- 构造器(中)

构造器的继承和重写

跟 Objective-C 中的子类不同,Swift 中的子类不会默认继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。

假如你希望自定义的子类中能实现一个或多个跟父类相同的构造器,也许是为了完成一些定制的构造过程,你可以在你定制的子类中提供和重写与父类相同的构造器。

当你写一个父类中带有指定构造器的子类构造器时,你需要重写这个指定的构造器。因此,你必须在定义子类构造器时带上override修饰符。即使你重写系统提供的默认构造器也需要带上override修饰符。

相反地,如果你写了一个和父类便利构造器相匹配的子类构造器,子类都不能直接调用父类的便利构造器,每个规则都在上文构造器链有所描述。

class Vehicle {
var numberOfWheels = 0
// 为存储属性设置了默认值,所以不必自定义构造器,Swift也会生成一个默认构造器"init()"
var discription: String {
return "\(numberOfWheels) wheel(s)"
}
} class Bicycle: Vehicle {
// 子类自定义了一个构造器,这个构造器与父类的默认构造器相匹配,所以要加上关键字 "override"
override init() {
super.init()
numberOfWheels = 2
}
}

自动构造器的继承

如上所述,子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的。在实践中,这意味着对于许多常见场景你不必重写父类的构造器,并且在尽可能安全的情况下以最小的代价来继承父类的构造器。

假设要为子类中引入的任意新属性提供默认值,请遵守以下2个规则:

  1. 如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
  2. 如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。

即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。

演练

class Food {
var name: String
// 指定构造器
init(name: String) {
self.name = name
} // 便利构造器
convenience init() {
self.init(name: "[没有商品名]")
}
} class RecipeIngredient: Food {
var quantity: Int // 指定构造器
init(name: String, quantity: Int) {
// 这里遵循Swift的安全检查
// 1、确保子类的存储属性有值
self.quantity = quantity
// 2、根据构造链向上调用父类的指定构造器,使父类的所有存储属性有值
super.init(name: name)
} // 这里将父类的指定构造器重写为子类的便利构造器
override convenience init(name: String){
self.init(name: name, quantity: 1)
} // 子类提供了父类所有的指定构造器,所以子类也默认继承了父类的所有便利构造器,这满足了规则2:(如果子类提供了所有父类指定构造器的实现--不管是通过规则1继承过来的,还是通过自定义实现的--它将自动继承所有父类的便利构造器。)
} class ShoppingListItem: RecipeIngredient {
var purchased = false // 是否购买
var discription: String { // 描述
var output = "\(quantity) x \(name.lowercaseString)"
output += purchased ? "√" : "×"
return output
}
// 注意:这个子类没有定义任何指定构造器,那么它将遵守规则1 自动继承所有父类的指定构造器
} let salt = RecipeIngredient(name: "盐", quantity: 2)
let sugar = RecipeIngredient()
print("sugar name \(sugar.name), quantity \(sugar.quantity)")
// 打印出:"sugar name [没有商品名], quantity 1\n"
// 为什么quantity属性会是1? 因为sugar初始化的时候调用的是继承来的便利构造器,在便利构造器中“(init())”父类设置它将构造过程代理给父类的指定构造器"init(name: String)",不过这个便利构造器被继承后不再调用父类的"init(name: String)",而是子类override的"(convenience init(name: String))" // 开始创建购物清单
var list = [ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem(name: "Eggs", quantity: 5)]
// 可以看到 ShoppingListItem这个子类继承了所有父类的构造方法 list[1].purchased = true for item in list {
print(item.discription)
}
// 1 x [没有商品名]×
// 1 x bacon√
// 5 x eggs×