【官方文档翻译】Swift 4.0.3 ☞ Initialization

原文链接

初始化(Initialization)

Initialization是为了使用一个类、结构体或者枚举实例的准备过程。这个过程牵涉到给实例的每个存储的属性设置一个初始值和使用之前所需的任何其他设置或者初始化

你通过定义一个initializers构造器实现这个初始化过程,这是一个可以创建指定的类新实例的特殊方法。与OC的构造方法不同的是,Swift构造器不用返回值。它们的主要作用是确保一个新实例在它们第一次使用的时候被正确的初始化。

类的实例也可以实现一个deinitializer析构方法,用来当这个类的实例被销毁之前执行一些清理工作。更多的内容参见Deinitialization

给被存储的属性设置初始值(Setting Initial Values for Stored Properties)
类和结构体的实例在被创建之前必须给它们所有的属性设置一个合适的初始值。存储的属性不能处于不确定状态。

你可以在构造器内部给一个属性设置初始值,或者在它们被定义的时候赋一个默认值。这些实现会在下面讲解到。

注意
当你给存储属性一个默认值,或者在它的构造方法里设置它的初始值,这些方式都是直接设置属性,不会调用任何属性观察者。

构造方法(Initializers)

构造方法在特定的类创建新实例的时候被调用。用它最简单的方法,一个类似无参数实例方法的构造器,使用init关键字

init() {
     // perform some initialization here
}

下面这个例子定义了一个Fahrenheit的结构体在华氏温度的范围内存储一个温度。在Fahrenheit结构体中有一个存储属性,temperature,被定义为Double类:

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature) Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit"

这个机构体定义了唯一的构造器,init,没有参数,在这个方法里初始化了temperature赋值为32.0。

默认属性值(Default Property Values)

你可以像上面一样,在构造器里给存储属性赋初始值,此外,指定一个默认值作为属性声明的一部分。你可以在属性被定义的时候给属性赋上一个初始值。

注意
如果一个属性一直使用初始值,我们采用在定义的时候赋初始值,而不是在构造器中赋值。虽然结果一样,但是赋默认值它和定义紧密的联系在一起,这样做使代码更简短、更清晰并且可以使你从初始值推断属性的类型。默认值也让你更方便利用默认构造器和继承,就像这节描述的这样。

你可以将上面的Fahrenheit结构体用一种更简单的方式写出来,通过给它的temperature属性在被定义的时候赋初值:

struct Fahrenheit {
    car temperature = 32.0
}

自定义初始化(Customizing Initialization)

你可以像下面这一节描述的那样,在初始化时通过传入参数、使用可选类型的属性或者在初始化过程中给常量属性赋值来自定义初始化过程。

初始化参数(Initialization Parameters)

你可以提供初始化参数作为定义构造函数的一部分,去定义传入值的类型和名称。初始化参数与函数和方法参数一样,具备同样的作用和语法。

下面这个例子定义了一个Celsius结构体,Celsius结构体实现了两个自定义的构造器,分别叫init(fromFahrenheit:)和init(fromKelvin:),可以从不同的温度模式下创建新实例。

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }

    init(fromKelvin kelvin: Double) {
        temperatureIncelsius = kelvin - 237.15
    }
}

let boilingPointOfWater = Celsius(fromfahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

第一个构造器有一个构造参数使用了参数标签fromFahrenheit和参数名称fahrenheit。第二个构造器有一个构造参数使用了参数标签fromKelvin和一个参数名称为kelvin。两个构造器转化他们传入的参数成为合理的值,并将它存入temperatureInCelsius中。

参数名称和参数标签(Parameter Names and Argument Labels)

像函数和方法的参数一样,当调用构造方法时,初始化参数可以同时拥有一个在构造器方法中使用的名称和一个参数标签。

然而,构造器不能像函数和方法一样通过圆括号前的函数名称确定。因此,在确定哪个构造方法应该被调用时,构造参数的名称和类型起到了一个重要的作用。正因为如此,如果你没有提供参数标签,Swift为构造函数提供了自动的参数标签。

下面的例子定义了Color结构体,拥有red,green,和blue三个常量属性。这些属性存储0.0到1.0之间的值表明颜色的数值。

Color提供了一个构造函数使用三个Double类型的参数给对应的参数,还提供了一个参数的构造函数,给所有参数提供相同的值

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red = red
        self.green = green
        self.blue = blue
    }

    init(white: Double) {
        red     = white
        green = white
        blue    = white
    }
}

两个构造函数都可以创建新的Color实例,通过给每个构造参数提供数值

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

注意到调用这些构造函数不使用参数标签是不行的。只要定义了构造函数就必须使用参数标签,忽略它就会编译报错。

let veryGreen = Color(0.0, 1.0,10.0)
// this reports a compile-time error - argument labels are required

没有参数标签的构造器参数(Initializer Parameters Without Argument Labels)

如果你不想在构造器参数中使用参数标签,写一个下划线(_),覆盖默认添加标签的行为。

这里有一个上面Celsius例子的延伸版本,添加了一个额外的构造器去创建一个Celsius实例,在相同类型下,使用Double类型数值创建Celsius实例。

struct Celsius {
   var temperatureInCelsius: Double
   init(fromFahrenheit fahrenheit: Double) {
       temperatureInCelsius = (fahrenheit - 32.0) / 1.8
   }
   init(fromKelvin kelvin: Double) {
       temperatureInCelsius = kelvin - 273.15
   }
   init(_ celsius: Double) {
       temperatureInCelsius = celsius
   }
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0

这个构造器调用Celsius(37.0)是足够清楚的在不适用参数标签情况下。因此像这样写init(_ celsiue: Double)构造器是合适的。

可选类型属性(Optional Property Types)

如果你的自定义的类有一个属性的逻辑允许为空值--可能因为它的值不能在初始化的过程中被设置,或者因为在以后的某个时间点允许为空值--我们就声明这个属性为可选类型。可选类型的属性在被初始化的过程中自动赋值为nil,指明这个属性有可能在初始化期间没有值。

下面这个例子定义了一个SurveyQuestion类,有一个可选类型的response属性:

class SurveyQuestion {
    var text: String
    var response: String?
    init(text: String) {
       self.text = text
    }
 }

func ask() {
     print(text)
}

let cheseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// Prints "Do you like cheese"
cheeseQuestion.response = "Yes, I do like cheese"

问题的答案在问之前都是未知的,所以response属性被声明为String?,或者叫“optional String”。当以个SurveyQuestion被初始化时,自动地被赋值为nil,意味着还没有值。

在初始化过程中赋值常量属性(Assigning Constant Properties During Initialization)

你可以在初始化过程中的任何时间给一个常量属性赋值,只要在初始化完成时被设置一个确定的值。一旦常量属性被赋值,永远不能更改。

注意
对于类实例而言,一个常量的属性可以在初始化过程中被修改,仅仅在当前类中,不能被子类修改。

你可以重写上面SurveyQuestion的例子,把text写为常量属性而不是变量属性,指明了SurveyQuestion实例一旦被创建就不能修改

class SurbeyQuestion {
    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()
// Prints "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)

默认的构造器(Default Initializers)

当一个类或者结构体没有提供任何一个构造器时,Swift 提供一个默认的构造器给所有的结构体和类,这个构造器给所有的属性提供默认值。这个默认的构造器通过给所有属性赋默认值来简单的创建一个实例。

下面这个例子定义了一个ShoppingListItem类,封装了购物单的name,quantity和purch项。

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

因为ShoppingListItem类的所有属性有默认值,并且因为它是一个基类,没有父类,所以ShoppingListItem自动的获得默认的构造器去创建一个新实例,这个新实例的属性均为默认值。(name属性是一个可选字符串属性,所以它自动设置为nil,即使没有明确的写出来)。上面这个例子使用了默认的构造器去创建一个ShoppingListItem新实例,使用了构造器语法,被写为 ShoppingListItem(),并把它赋给了一个变量。

结构体类型的逐一成员构造器(Memberwise Initializers for Structure Types)

如果结构体类型没有自定义任何的构造器,他们将自动的接收一个逐一成员构造器。与默认构造器不一样,结构体类型接收一个逐一成员构造器即使被存储的属性不是默认值。

逐一成员构造器是一个快速的方式初始化一个新结构体实例的成员属性,新实例属性的初始值可以通过注意成员构造器依靠名称传递。

下面的例子定义了一个Size结构体,有两个属性,分别叫做width和height。这两个属性通过赋初始值0.0被推断为Double类型。

Size结构体自动接收一个init(width:height:)逐一成员构造器,当你初始化一个新实例:

struct Size {
      var width = 0.0,height = 0.0
}

let twoByTwo = Size(width: 2.0, height:2.0)

数值类型的构造代理(Initializer Delegation for Value Types)

在一个实例的初始化过程中,可以在自己的构造器中调用其他的构造器作为构造自己的一部分。这个过程被称为 initializer delegation,避免了在构造过程中代码重复。

构造代理如何工作以及构造代理的形式在值类型 和 对象类型的规则是不一样的。值类型(结构体和枚举)没有继承,所以他们的构造代理过程相对简单,因为它们的代理只能提供它们自己的构造方法。但是,Classes类型,可以继承其他类,正如被描述的这样Inheritance。这意味着class类型有责任去保证他们继承的所有属性在初始化过程中都被赋上合适的值。这些要求阐述在Class Inheritance and Initialization。

对于值类型,你可以在自定义的构造器中调用self.init去调用相同类型的其它构造器。你可以在一个构造器内部调用self.init.

注意如果你给一个值类型自定义了一个构造器,你就不能再调用这个类型的默认构造器(如果是结构体类型,就是逐一成员构造器)。这个限制避免了一种情况,就是有时需要在更复杂的构造器中添加一些其它设置,却不小心调用了默认构造器。

理解:结构体也有默认构造器,如果你自定义了构造器,是可以调用结构体的默认构造器的,只是不能调用结构体的逐一成员构造器(重写除外)。

注意
如果你想自己定义的值类型即可以使用默认构造器或者逐一成员构造器也能使用自定义的构造器,你需要在Extensions中写自动义的构造方法,而不是在类的实现文件中。更多的信息,请参考Extensions。

下面这个例子定义了一个Rect结构体去表示一个几何矩形。这个例子需要两个支持的结构体,分别叫做Size 和 Point,他们的属性初始值都被设置为0.0

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

你可以用三种方式初始化一个Rect机构体-- 通过使用默认的zero-initialized 初始化size 和 point属性,通过提供一个具体的origin point 和 size,或者通过提供一个中心点和size。这三种可选择的构造方法作为机构体定义的一部分:

struct Rect {
    var  origin = Point()
    var size = Size()
    init() {}
    init(oringin: 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(origin: Point(x: originX, y: originY), size: size)
    }
}

第一个构造器,功能和没有自定义构造器时接收的默认构造器一样。这个构造器有一个空的函数体,用{}表示。调用这个构造器返回一个Rect实例,它的origin和size属性都被初始化为定义时候的值:
let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

第二个构造器,init(origin:size:),功能和构造器没有自定义构造器时接收的逐一成员构造器一样。这个构造器简单的指定了origin和size参数给存储属性。

let originRect = Rect(origin: Point(x: 2.0, y:2.0), size: Size(width: 5.0, height:5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)

第三个构造器,init(center:size), 有一些复杂,它基于center点计算出origin点 和 size 去构造。然后调用(或者代理)init(origin:size:)构造器,给对应的属性存储新值。

let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

init(center:size:)构造器会给它对应的属性赋新值,但是,它更方便。

注意
还有一种方法,你自己不用定义init() 和 init(origin:size),参看 Extensions。

类继承和初始化(Class Inheritance and Initialization)

所有类的存储属性,包括从父类继承来的所有属性--在初始化的过程中必须赋值。

Swift给class类型定义了两种构造器,保证所有的存储属性接收一个初始值。它们被称为指定构造器(designated initializers) 和 便利构造器(convenience initializers)。

指定构造器和便利构造器

Designated initializers 是一个类的主要构造器。指定构造器完全初始化类的所有属性并且在父类链上调用父类的初始化方法。

类有可能有几个指定构造器,大部分类只有一个指定构造器。当初始化过程发生时,指定构造器是“漏斗”点,并且通过它在继承链上继续。

每个类都必须至少有一个指定构造器。有些情况下,需要通过从父类继承一个或者多个指定构造器,详情参看Automatic Initializer Inheritance。

便利构造器是次要的,给一个类提供构造器。你可以通过在同一个类中调用指定构造器并传入特定的值来定义一个便利构造器。你也可以为一些特殊的使用情况或者输出值类型创建一个便利构造器。

如果你的类不需要便利构造器,就不用提供。创建便利构造器起到了便利、清晰的作用。

指定构造器和便利构造器的语法(Syntax for Designated and Convenience Initializers)

类的指定构造器写法和值类型的简单构造器一样:

init(`parameters`) {
       `statements`
}

类Type的指定构造器(Initializer Delegation for Class Types)

为了简单的描述指定构造器和便利构造器之间的关系,Swift遵循下面三条构造器之间的代理调用:

规则1:

指定构造器必须从调用它直属父类的指定构造器。

规则2:

便利构造器必须调用同类的构造器。

规则3:

便利构造器最终必须调用指定构造器。

简单的记法:

  • 指定构造器必须向上代理
  • 便利构造器必须横向代理

看下图:


【官方文档翻译】Swift 4.0.3 ☞ Initialization_第1张图片

这里,父类有唯一一个指定构造器和两个便利构造器。一个便利构造器调用了另一个便利构造器,另一个构造器又调用了指定构造器。这满足了上面提到的规则2和规则3,父类上面没有父类,所以不用遵循规则1。

这幅图里的子类有两个指定构造器和一个便利构造器。便利构造器必须调用其中一个指定构造器,因为它只能在同类中调用。这符合上面的规则2和规则3。所有的指定构造器必须调用父类唯一的指定构造器,符合规则1。

注意
这些规则不影响如何使用你的类创建实例,上面的图表中,任何的构造器都可以被用于创建一个完全初始化的该类的实例,这些规则只影响你书写类的构造器的实现方法。

下面这个图展示了4个类更复杂的继承关系。它说明了在类的初始化过程中指定构造器是如何作为“漏斗”的。

【官方文档翻译】Swift 4.0.3 ☞ Initialization_第2张图片

两阶段初始化 (Two-Phase Initialization)

在Swift中,类的初始化是一个两阶段过程。第一个阶段,给每个类引用的属性赋初始值。一旦这个过程完成,开始第二个阶段,每个类有机会在被使用前自定义它们的存储属性

使用两阶段初始化过程是安全的,同时,在类的继承中,仍然给予了完成的灵活性。两个阶段初始化避免了属性在没有初始化完成的情况下被访问,而且避免了属性被另一个构造器设置不期望的值

注意
Swift的两段初始化过程和OC相似。主要的不同在于第一个阶段,OC赋值0或者null(例如0或者nil)给每个属性。Swift的初始化更灵活在于让你自定义属性的初始值,并且没有设置值时赋0或者nil。

理解:这里说的不同,指的是我们使用Swift时,可以像let A = 0.0 这样赋初始值

Swift的编译器执行4个有用的安全检查保证两段初始化没有错误完成。

安全检查 1

一个指定构造器在向上代理父类的的构造器之前必须确保这个类的所有属性都被初始化。

像上面提到的那样,一个对象的内存当它所有的属性都被初始化时才被认为完全初始化。满足这一点,一个指定构造器在沿构造链向上之前必须初始化自己的所有属性。

安全检查 2

一个指定构造器必须在设置继承类的属性之前,必须向上代理父类的构造器,如果没有这么做,这个指定构造器赋的新值会在父类的初始化过程中被覆盖。

安全检查 3

便利构造器在给任何属性赋值时必须横向代理其他构造器(包括被同类定义的属性)。如果不这么做,这个便利构造器赋的新值会被所在类的指定构造器覆盖。

安全检查 4

在第一个阶段没有完成时,一个构造器不能调用任何实例方法,获得实例的属性值,或者使用self作为值。

类的实例在第一阶段完成前不是完全有效的。只有在第一个阶段完成后,类的实例才被成为有效,你才可以访问属性,调用实例的方法。

这里基于上面的四条安全检查,展示了两段初始化如何完成。

第一个阶段
  • 指定构造器或者便利构造器在类中被调用。

  • 新实例的内存被分配。内存仍然没有被初始化

  • 类的指定构造器确认所有引用的属性都有值。现在,这些存储属性的内存被初始化。

  • 指定构造器交给父类构造器为它的属性执行同样的操作。

  • 继续这个操作直到基类

  • 一旦到达继承链的顶端,并且基类确保它的所有属性都有值,此时,这个实例的内存被认为完全初始化,第一个阶段完成。

第二阶段
  • 从链的顶部开始,每个指定构造器有机会自定义实例。构造器现在可以访问self并且可以修改它的属性,调用实例的方法,等等。

  • 最终,所有的便利构造器有机会去自定义实例并且使用self。

这里展示了一个假设的子类和父类如何在第一个阶段的初始化。

【官方文档翻译】Swift 4.0.3 ☞ Initialization_第3张图片

在这个例子中,首先调用了子类的便利构造器。这个便利构造器仍然不能修改任何属性。它横向代理给同类的指定构造器。

指定构造器确保子类的所有属性有初始值,像安全检查1描述的那样。然后沿着继承链调用它的父类的构造方法。

父类的指定构造器确保了父类的所有属性都有值,因为更多的父类,所以不必继续向上代理。

一旦父类的所有属性都有值,它的内存被认为完全初始化,第一个阶段完成。

下面看一下同一个初始化过程第二阶段:

【官方文档翻译】Swift 4.0.3 ☞ Initialization_第4张图片

父类的指定狗仔器现在有机会去自定义实例(虽然不是必须的)。

一旦父类的指定构造器完成,子类的指定构造器可以自定义(同理,不是必须的)。

最终,一旦子类的指定构造器完成,便利构造器(最初被调用的那个)可以添加自定义操作。

构造器的继承和重写(Initializer Inheritance and Overriding)

与OC的子类不同,Swift子类默认不继承他们父类的构造器。Swift避免了一种情况,一个更特别的子类通过继承父类的简单构造器完成初始化,导致没有完全 或者 正确初始化。

注意
父类构造器在一些情况下被继承,但是只有在安全的情况下适合这样做。更多的阐述,参看Automatic Initializer Inheritance。

如果你想定制一个子类去实现一个或者多个和父类相同的构造器,你要在子类中提供自定义的实现。

当你写一个子类构造器和父类的指定构造器相同时,你需要通过重写使得这个指定构造器生效。因此,你必须写在子类构造器定义的前面写上override修饰符。即便你重写了默认构造器,你也应该添加修饰符。参看Default Initializers。

作为一个被重写的属性、方法或者下标,override修饰符的出现会促使Swift检查父类有一个相同的指定构造器被重写,并且验证你的重写的构造器的参数是否指定正确。

注意
即使你的子类实现了父类的指定构造器作为便利构造器,你也要使用override修饰符。

相反的,如果你写的子类的构造器和父类的便利构造器一样,父类的便利构造器永远不会被你的子类直接调用,像上面描述的那样Initializer Delegation for Class Types。因此,严格的说你的子类没有重写父类的构造器。因此,你不用写override修饰符。

下面这个例子定义了一个Vehicle基类。这个基类声明了numberOfWheels的存储属性,赋初始值0。这个numberOfWheels属性被用于一个叫description的计算属性用于描述车的特征:

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

Vehicle类提供了一个默认值给它的存储属性,并且没有提供任何的构造器。因此,它自动接收一个默认构造器,参看Default Initializers。这个默认构造器肯定是类的指定构造器,可以用来创建一个Vehicle实例,numberOfWheels为0:

let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)

下面的例子定义了Vehicle的子类 叫做Bicycle:

class Bicycle{
    override init (){ 
        super.init()
        numberOfwhieels = 2
    }
}

Bicycle类自定义了一个指定构造器,init()。这个指定构造器和父类的指定构造器相同所以Bicycle版本的构造器需要被override修饰。

Bicycle的构造器init()方法中首先调用了父类的super.init(),调用了父类的默认构造器,这确保了Bicycle修改从父类继承来的numberOfWheels属性之前,numberOfWheels已经被父类Vehicle初始化。调用super.init()之后,numberOfWheels的初始值被修改为2.

如果你创建了一个Bicycle实例,你可以调用它的继承来的description计算属性看一看numberOfWheels属性是否被更新了:

let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 whieel(s)

注意
子类可以在初始化过程中修改继承来的变量属性,但是不能修改继承来的常量属性。

自动构造器继承(Automatic Initializer Inheritance)

像上面提到的,子类默认不能继承它们父类的构造器。但是,父类构造器在一些情况下会被自动继承。在开发中,这意味着在许多普通的情况下,你不需要重写构造器,你可以很轻松继承父类构造器,如果这么做是安全的。

假如你给子类所有引用的属性提供了默认值,就符合下面的两条规则:

规则 1

如果你的子类没有定义任何的指定构造器,它自动继承父类的所有指定构造器。

规则 2

如果你的子类提供了它的父类所有指定构造器的实现-- 要么通过规则1那样继承它们,要么自己定义--它将自动的继承父类所有的便利构造器。(If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.)

即使子类定义了更多的便利构造器,这些规则同样适用。

注意
根据规则2,子类可以实现父类的指定构造器作为自己的便利构造器。

指定构造器和便利构造器的应用(Designated and Convenience Initializers in Action)

下面这个例子展示了指定构造器、便利构造器、和自动继承的实现。这个例子定义了三个类的继承分别叫做Food,RecipeIngredient,和ShoppingListItem,并且展示了他们的构造器如何交互。

Food是基类,这是一个简单的类,封装了所有食品的名称。Food类引用了一个String类型的name属性,提供了两个构造器创建Food实例:

class Food {
    var name: String
    init(name: String) {
         self.name = name
    }
   convenience init() {
       self.init(name: "[Unnamed]")
   }
}

下面这个图展示了Food类的构造器链

【官方文档翻译】Swift 4.0.3 ☞ Initialization_第5张图片

类是没有默认的逐一成员构造器的,所以Food累提供了一个带name参数的指定构造器。这个构造器可以被用来创建一个指定名称的Food实例:

let nameMEat = Food(name: "Bacon")
// nameMeat's name is "Bacon"

init(name: String) 构造器是Food类提供的指定构造器,因为它可以保证新实例的所有属性被完全初始化。Food类没有父类,所以init(name: Stirng)构造方法中不需要调用super.init()去完成它的初始化。

Food类也提供了一个便利构造器,init(),没有参数,这个init()构造器通过横向调用Food类的指定构造器init(name: String),创建name为[Unnamed]的新的food实例:

let mysterMeat = Food()
// mysteryMeat's name is "[Unnamed]"

在这个继承关系中,第二个类叫RecipeIngredient,是Food的子类。RecipeIngredient类是食谱的组成模型。它引用了一个Int类型的quantity属性(在它的父类Food的name属性基础上,添加了quantity属性)并且定义了两个创建实例的构造方法:

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

下面的图片展示了RecipeIngredient类的构造链


【官方文档翻译】Swift 4.0.3 ☞ Initialization_第6张图片

RecipeIngredient类有一个指定构造器,init(name: Stirng, quantity: Int),可以初始化一个新的RecipeIngredient实例的所有属性。这个构造器首先把传递过来的quantity参数赋给quantity属性,这是RecipeIngredient仅有的新属性(相对于父类)然后,构造器向上代理父类的init(name: String)构造器。这个过程满足安全检查1Two-Phase Initialization。

RecipeIngredient也定义了一个便利构造器,init(name: String),被用于只根据name创建实例。这个便利构造器指定了所有的实例的quantity属性都为1。定义这个便利构造器使得RecipeIngredient实例更加快捷、方便的被创建,并且避免了创建几个quantity为1 的实例时代码重复。这个便利构造器简单的通过横向代理类的指定构造器,传入quantity的值为1。

RecipeIngredient提供的便利构造器init(name: Stirng),和父类Food的指定构造器带有相同的参数。因为这个便利构造器重写了它父类的指定构造器,所以它必须用override修饰符修饰(参看Initializer Inheritance and Overriding

即便RecipeIngredient提供了init(name: String)构造器作为便利构造器,RecipeIngredient仍然算是实现了父类的所有指定构造器。因此,RecipeIngredient自动继承它的父类的所有便利构造器。

在这个例子里,recipeIngredient的父类是Food,有一个便利构造器叫做init()。这个构造器因此被RecipeIngredient继承。被继承来的init()函数调用和在父类中完全一样,但是它代理的调用方法init(name: Stirng) 是RecipeIngredient类的不是父类的。

现在有三种构造方法可以用于创建RecipeIngredient实例:

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecieIngredient(name: "Eggs", quantity: 6)

最后一个类继承RecipeIngredient,叫做ShoppinglistItem。这个类表示了食谱在购物清单里的展现。

购物单中的每个项目,起初都是“未购买的”。表达这个意思,ShoppintListItem引用了一个布尔属性叫做purchased,赋初始值为false。ShoppingListItem也添加了一个计算属性description,为ShoppingListItem实例提供了一个文本描述。

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
}

注意
ShoppingListItem没有通过构造器给purchased提供初始值,因为在购物单中的item开始总是未被购买的。

因为它给所有引用的属性提供了默认值并且自身没有定义任何构造器,ShoppingListItem自动从它的父类继承所有的指定构造器和便利构造器

理解:上面就解释了规则2。

下面这幅图显示了这三个类完整的构造器链:

【官方文档翻译】Swift 4.0.3 ☞ Initialization_第7张图片

你现在可以使用继承来的所有构造器创建一个ShoppingListItem实例:

var breakfastList = [
       ShoppingListItem(),
       ShoppingListItem(name: "Bacon"),
       ShoppingListItem(name: "Eggs", quantity: 6),
]

breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true

for item in breakfastList {
    pring(item.description)
}

// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘

这里,一个新的breakfastList数组通过一个包含三个ShoppingListItem实例的字面量数组被创建。这个数组的类型被推断为[ShoppingListItem]。这个数组被创建后,第一个元素的name从“[Unnamed]”被改为“Orange juice” 并且被标记为已购买。通过打印每个item的description显示出他们的默认值已经像预期那样设置好了。

可失败的构造器(Failable Initializers)

有时需要定义一个类、结构体、或者枚举,允许它的初始化过程失败。这个失败可能是因为无效的初始化参数、缺少外界必须的资源、或者一些其他的组织初始化成功的情况。

处理初始化可以失败的情形,定义一个或者多个可失败构造器作为类、结构体、枚举的一部分,你通过在init后面添加一个问号init? 表示一个可失败构造器。

注意
你不能用相同的参数类型和名称同时定义一个可失败的和不可失败的构造器。

一个可失败的构造器创建它构造类型的可选值。你在可失败的构造器中写return nil来指示初始化失败被调用的时刻。

注意
严格的来说,构造器不能返回值。它们的作用是确保初始化完成时,自己已经被完全和正确的初始化。即使你写return nil 调用初始化失败,你不能使用return去表示初始化成功。

未完待续。。。

你可能感兴趣的:(【官方文档翻译】Swift 4.0.3 ☞ Initialization)