十三、初始化 Initialization

1. 概述

通过定义构造器 initializers 创建类、结构体和枚举的一个实例。与Objective-C的 initializers 不同的是,Swift中的 initializers 没有返回值。

类类型 class types 的实例也可以实现析构器 deinitializer,在类被释放之前,进行清理工作。

 

2. stored properties的初始化(Setting Initial Values for Stored Properties)

类和结构体的 stored properties 在他们的实例被创建的时候,就必须有合适的值。

你可以在构造器中初始化他们,也可以在定义的时候给他们设置默认值。

注意:当给 stored property 初始化的时候,不论是使用默认值初始化,还是调用构造器初始化,都不会调用属性监视器。

 

2.1 构造器 Initializers

构造器是使用 init 关键字定义的没有参数的方法,用来创建某种类型的实例:

    init() {
      // perform some initialization here
    }

如:

    struct Fahrenheit {  //华氏温度
      var temperature: Double
      init() {
        temperature = 32.0
      }
    }
    var f = Fahrenheit()
    println("The default temperature is \(f.temperature)° Fahrenheit")
    // prints "The default temperature is 32.0° Fahrenheit"

 

2.2 默认参数值 Default Property Values

当某个参数总是有某个相同的初始化值时,使用默认参数值比在构造器中初始化它要好。当然他们的结果是一样

    struct Fahrenheit {
      var temperature = 32.0
    }

 

3. 自定义初始化方法 Customizing Initialization

下面的例子定义了结构体Celsius的两个初始化器,用于从不同的温度进行初始化:

    struct Celsius { //摄氏度
      var temperatureInCelsius: Double
      init(fromFahrenheit fahrenheit: Double) { //由华氏温度得到摄氏温度
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
      }
      init(fromKelvin kelvin: Double) { //由kelvin温度得到摄氏温度
        temperatureInCelsius = kelvin - 273.15
      }
    }
    let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
    // boilingPointOfWater.temperatureInCelsius is 100.0
    let freezingPointOfWater = Celsius(fromKelvin: 273.15)
    // freezingPointOfWater.temperatureInCelsius is 0.0

 

4. 内部和外部参数名 Local and External Parameter Names

如果你没有给 init 方法外部参数名,编译器会自动给构造器添加外部参数名,这个外部参数名与内部参数名同名。

    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
      }
    }

    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, 0.0)
    // this reports a compile-time error - external names are required

 

5. 没有外部参数名的构造器参数 Initializer Parameters Without External Names

如果你不希望构造函数使用外部参数名,使用下划线(_)覆盖编译器默认行为。

    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) //37.0

 

6. 可选值属性 Optional Property Types

当属性为可选值类型 optional type 时,它会被自动初始化为 nil ,表明它在初始化期间还没有值。

    class SurveyQuestion {
      var text: String
      var response: String? //automatically assigned a default value of nil
      init(text: String) {
        self.text = text
      }
      func ask() {
        println(text)
      }
    }
    let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
    cheeseQuestion.ask()
    // prints "Do you like cheese?"
    cheeseQuestion.response = "Yes, I do like cheese."

 

7. 在初始化期间更改Constant Properties (Modifying Constant Properties During Initialization)

你可以在初始化期间更改 Constant Properties 的值,只要你在初始化完毕后能给它一个确切的值。

对类的实例来说,Constant Properties 只能在初始化时被改变。并且Constant Properties 不能被它的子类改变。

    class SurveyQuestion {
      let text: String
      var response: String?
      init(text: String) {
        self.text = text
      }
      func ask() {
        println(text)
      }
    }
    let beetsQuestion = SurveyQuestion(text: "How about beets?")
    beetsQuestion.ask()
    // prints "How about beets?"
    beetsQuestion.response = "I also like beets. (But not with cheese.)"

上面的代码中,text 为constant,那么它只能在init方法中被更改,一旦创建了一个SurveyQuestion 的实例,那么text 就再也不能更改了。

 

8. 默认构造器 Default Initializers

8.1 基类的默认构造器

Swift为下面的类型提供默认构造器:

  • 1)所有的结构体。
  • 2)本身没有提供任何构造器的基类。

默认构造器只是创建给所有的属性提供默认值的实例。

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

 

8.2 成员逐一初始化的结构体默认构造器 Memberwise Initializers for Structure Types

如果你没有为结构体提供自定义的构造器话,编译器将为结构体提供一个成员逐个初始化的构造器 memberwise initializer

    struct Size {
      var width = 0.0, height = 0.0
    }
    let twoByTwo = Size(width: 2.0, height: 2.0)

如果你想让默认构造器和自定义构造器并存,可以将自定义构造器写在扩展Extention中,后面会介绍Extention。

 

9. 值类型的构造器代理

一个构造器能够调用其他构造器去实例化自己的部分内容,这就是构造器代理。构造器代理可以避免代码的重复冗余。

    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(origin: Point(x: originX, y: originY), size: size)
      }
    }

结构体Rect的第一个构造器没有参数,函数体为空,它与默认构造器相同,返回属性的默认初始值。

    let basicRect = Rect()
    // basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

第二个构造器与 memberwise initializer 相同——如果没有自定义构造器的话。

    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)

 

10. 类的初始化 Class Inheritance and Initialization

Swift 提供两种构造器:designated initializers 和 convenience initializers。

 

10.1 designated initializers

  • 1)designated initializers 是类的第一构造器(primary initializers),它初始化一个类引入的所有属性,并且向上调用父类构造器初始化整个继承链条。
  • 2)类至少要有一个 designated initializers。大部分情况下类只有一个 designated initializers,子类可以继承父类的 designated initializers。

 

10.2 convenience initializers

  • 1)convenience initializers 是第二构造器(secondary initializers),它调用 designated initializers 对类进行初始化。
  • 2)类可以没有 designated initializers。
  • 3)designated initializers 是类的构造器的速记方法。

 

11. 两种构造器的语法

1)designated initializer

2)convenience initializers

 

12. 类的构造器代理 Initializer Delegation for Class Types

12.1 两种构造器必须满足以下关系:

  • 1)designated 构造器必须调用它的直接父类的 designated 构造器
  • 2)convenience 构造器必须调用同一个类中的其他构造器。
  • 3)convenience 构造器最终必须调用designated 构造器。

可以按以下方式记忆:

  • designated 构造器总是向上代理。(Designated initializers must always delegate up.
  • convenience 构造器总是横向代理。(Convenience initializers must always delegate across.)

image: ../Art/initializerDelegation01_2x.png

如图所示:

Superclass 有一个 Designated构造器和两个 Convenience构造器。一个Convenience构造器调用另一个Convenience构造器,最终调用Designated构造器。

Subclass 有两个 Designated构造器和一个 Convenience构造器,Convenience构造器必须调用其中的一个Designated构造器,因为它必须调用同一个类中的其他构造器。所有的Designated构造器都必须调用父类 Superclass的 Designated构造器。

下图展示了一种更复杂的情况:

image: ../Art/initializerDelegation02_2x.png

 

12.2 两阶段初始化 Two-Phase Initialization

Swift中类的初始化分两阶段进行:

  • 1)第一阶段,所有的 Stored Property 被引进它的类赋予一个初始化值,一旦每个Stored Property 的初始化值确定了以后,第二阶段初始化就开始了。
  • 2)第二阶段,每个类都有机会对 Stored Property 进行进一步的定制。

两阶段初始化使得类的初始化更加安全,防止属性在值确定之前被访问,并且防止被其他构造器意外赋值。同时使得初始化具有灵活性。

为了不使初始化出错,Swift编译器进行以下安全检查:

  • 1)安全检查1:Designated构造器必须保证,它所在类引入的属性都已经被初始化了,然后才能向上代理调用父类的构造器。因为,只有当一个对象的所有Stored属性都是初始化了的时候,它的存储空间才是被初始化了的(可用)。
  • 2)安全检查2:Designated构造器必须先向上代理调用父类的构造器,然后才能给继承来的属性赋值。否则,你的赋值将被父类构造器覆盖。
  • 3)安全检查3:Convenience构造器必须先代理调用其他构造器,然后再给其他属性(自己的和继承来的)辅助。否则,你的赋值将被这个类的Designated构造器覆盖。
  • 4)安全检查4:只有第一阶段初始化完毕后,构造器才能访问实例的方法和属性,才能使用 self 关键字。

初始化自己的属性 -->  调用父类构造器  --> 给继承来的属性赋值

基于上面的检查,两阶段初始化如下工作:

1)第一阶段:

  • 调用 Designated构造器或 Convenience构造器。
  • 给新实例分配内存,内存还未初始化。
  • Designated构造器确保这个类引入的所有Stored属性有值,即存储这些Stored属性的内存被初始化。
  • Convenience构造器调用父类的构造器,确保父类的Stored属性有值。
  • 继续进行上一步,直到达到继承链条的最顶层(基类)。
  • 一旦达到了继承链条的最顶层,最后的类确保它的所有Stored属性有值。这时这个实例的内存空间就被完全初始化了,第一阶段完成。

2)第二阶段:

从继承链条的最顶层往下,每一个 Designated构造器都有机会进一步定制实例。这时构造器可以使用 self 、可以修改属性、可以调用实例方法等等。

最后,继承链条中的所有的 Convenience构造器都有机会定制实例,并且使用 self。

下图展示了在假定的子类和父类之间构造的阶段1:

image: ../Art/twoPhaseInitialization01_2x.png

在这个例子中,构造过程从对子类中一个Convenience构造器的调用开始。这个Convenience构造器此时没法修改任何属性,它把构造任务代理给同一类中的Designated构造器。如安全检查1所示,Designated构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着造器链一直往上完成父类的构建过程。父类中的指定构造器确保所有父类的属性都有值。由于没有更多的父类需要构建,也就无需继续向上做构建代理。一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,而阶段1也已完成。

以下展示了相同构造过程的阶段2:

构建过程阶段2

父类中的Designated构造器现在有机会进一步来定制实例(尽管它没有这种必要)。一旦父类中的Designated构造器完成调用,子类的Designated构造器可以执行其他的定制操作(同样,它也没有这种必要)。最终,一旦子类的Designated构造器完成调用,最开始被调用的Convenience构造器可以执行其他的定制操作。

 

12.3 构造器的继承和覆盖 Initializer Inheritance and Overriding

与Objective-C不同,Swift的子类默认不继承父类的构造器。Swift的这种设定防止了在某些情况下,父类中一个简单的构造器被更专业的子类继承,或者使用父类中一个简单的构造器不完全、不正确的创建子类的实例。

在子类中实现新的构造器版本,使用override关键字覆盖父类构造器。

下面定义了一个基类 Vehicle:

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

因为Vehicle是基类,并且没有实现任何自定义的构造器,所以编译器会产生一个默认的构造器,它总是Designated类型构造器,它可以用来创建numberOfWheels为0的实例。

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

下面定义了一个Vehicle的子类:

    class Bicycle: Vehicle {
      override init() {//覆盖父类构造函数
        super.init() //先初始化父类
        numberOfWheels = 2  //再进一步定制
    }

    let bicycle = Bicycle()
    println("Bicycle: \(bicycle.description)")
    // Bicycle: 2 wheel(s)

子类可以在初始化时修改继承来的variable属性变量的值,但是不能修改继承来的constant属性。

 

12.4 构造器的自动继承 Automatic Initializer Inheritance

默认情况下,子类不会继承父类的构造器。但是如果满足某些情况,子类将自动继承父类构造器。这意味着,大部分情况下,你不需要覆盖父类的构造器,你可以继承父类的构造器,花很少的努力就可以让他可以被安全的使用。

假如你已经给子类引入的属性设置了初始值,那么以下规则将生效:

1)如果子类没有定义任何Designated构造器,它将自动继承父类所有的Designated构造器。

2)如果子类提供了父类所有Designated构造器的实现——不管是通过上面规则1)继承来的,还是提供了自定义的实现,它将自动继承父类所有的Convenience构造器。

即使子类增加了更多的Convenience构造器,上面的规则依然适用。

例如,定义一个基类:

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

继承链条如下:

image: ../Art/initializersExample01_2x.png

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

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

定义第二个类:

    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)
      }
    }

image: ../Art/initializersExample02_2x.png

它继承了父类的convenience init() 构造器。

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

定义第三个类:

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

因为它给所有自己引入的属性赋予了初始值,并且没有定义任何构造器,所以他将继承父类所有的Designated构造器和Convenience构造器。

image: ../Art/initializersExample03_2x.png

你可以它使用继承来的三个构造器初始化它:

    var breakfastList = [
      ShoppingListItem(),
      ShoppingListItem(name: "Bacon"),
      ShoppingListItem(name: "Eggs", quantity: 6),
    ]
    breakfastList[0].name = "Orange juice"
    breakfastList[0].purchased = true
    for item in breakfastList {
      println(item.description)
    }
    // 1 x Orange juice ✔
    // 1 x Bacon ✘
    // 6 x Eggs ✘

 

13. Failable构造器 Failable Initializers

 Failable构造器指可以失败的构造器。无效的初始化参数、缺少必要的外部资源或一些其他会让初始化失败的条件都可以触发Failable构造器。

为了应对这些可能会初始化的情况,可以使用( init? )关键字给结构体、枚举、类定义一个或多Failable构造器。

Failable构造器会给它所初始化的类型一个可选值Optional Value。在Failable构造器中使用 return nil 表明,这个时候Failable构造器可以被触发。

注意:严格的说,构造器并没有返回值,return nil 的作用仅仅表明Failable构造器这个时候可以被触发。

    struct Animal {
      let species: String
      init?(species: String) {
        if species.isEmpty { return nil } //如果species为空,构造失败被触发。否则对象将被成功实例化。
        self.species = species
      }
    }

检查Animal是否初始化成功:

    let someCreature = Animal(species: "Giraffe")
    // someCreature is of type Animal?, not Animal
    if let giraffe = someCreature {
      println("An animal was initialized with a species of \(giraffe.species)")
    }
    // prints "An animal was initialized with a species of Giraffe"

传空给Animal时:

    let anonymousCreature = Animal(species: "")
    // anonymousCreature is of type Animal?, not Animal
    if anonymousCreature == nil {
      println("The anonymous creature could not be initialized")
    }
    // prints "The anonymous creature could not be initialized"

 

13.1 枚举类型的 Failable构造器 (Failable Initializers for Enumerations)

    enum TemperatureUnit {
      case Kelvin, Celsius, Fahrenheit
      init?(symbol: Character) {
        switch symbol {
          case "K":
            self = .Kelvin
          case "C":
            self = .Celsius
          case "F":
            self = .Fahrenheit
          default:
            return nil
        }
      }
    }

使用Failable构造器选择合适的枚举成员:

    let fahrenheitUnit = TemperatureUnit(symbol: "F")
    if fahrenheitUnit != nil {
      println("This is a defined temperature unit, so initialization succeeded.")
    }
    // prints "This is a defined temperature unit, so initialization succeeded."
    let unknownUnit = TemperatureUnit(symbol: "X")
    if unknownUnit == nil {
      println("This is not a defined temperature unit, so initialization failed.")
    }
    // prints "This is not a defined temperature unit, so initialization failed."

 

13.2 使用Raw Values的枚举的Failable构造器 Failable Initializers for Enumerations with Raw Values

使用了Raw Values 的枚举类型默认拥有一个Failable构造器,init?(rawValue:)

我们可以重新定义TemperatureUnit 枚举:

    enum TemperatureUnit: Character {
      case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
    }
    let fahrenheitUnit = TemperatureUnit(rawValue: "F")
    if fahrenheitUnit != nil {
      println("This is a defined temperature unit, so initialization succeeded.")
    }
    // prints "This is a defined temperature unit, so initialization succeeded."
    let unknownUnit = TemperatureUnit(rawValue: "X")
    if unknownUnit == nil {
      println("This is not a defined temperature unit, so initialization failed.")
    }
    // prints "This is not a defined temperature unit, so initialization failed."

 

13.3 类的Failable构造器 Failable Initializers for Classes

 值类型Value Type(结构体、枚举)的Failable构造器能在任意时刻触发失败操作。但是类类型只有在这个类引入的所有的存储属性Stored Properties被赋予初始化值后,并且所有的构造代理发生后,才能触发Failable构造器的失败操作。

下面的例子展示了如何使用一个隐式解包可选值属性 implicitly unwrapped optional property 实现Failable构造器。

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

Product类拥有一个constant类型的name属性,它不能为空字符串。为了满足这个限制,我们使用一个Failable构造器确保当构造器成功初始化实例时,name一定非空。

与结构体不同的是,在触发失败的构造的时候,Product的Failable构造器必须给name提供一个初始值。

Product类的name属性是一个隐式解包可选值类型(String!),因为它是可选值类型,这意味着name属性在被初始化之前就有默认值nil,这个默认值nil意味着Product类的name属性有一个有效的初始值。那么在Failable构造器中给name指定某个具体的值之前,如果传入一个空串,就会触发构造失败操作。

因为name是Constant,如果构造成功,你可以确保它始终为非空值,在访问name之前, 也就不需要再检查name是否为空了。

    if let bowTie = Product(name: "bow tie") {
    // no need to check if bowTie.name == nil
      println("The product's name is \(bowTie.name)")
    }
    // prints "The product's name is bow tie"

 

13.4 Failable构造器的传播 Propagation of Initialization Failure

类、枚举和结构体中的Failable构造器可以使用它们中的其他Failable构造器,同样,子类也能访问父类中的Failable构造器。

无论哪种情况,一旦一个Failable构造器构造失败,整个构造器立即失败,不会继续执行初始化代码。

Failable构造器还可以调用非Failable构造器。

    class CartItem: Product {
      let quantity: Int! //隐式解包类型,可Product的name属性类似。
      init?(name: String, quantity: Int) {
        super.init(name: name)
        if quantity < 1 { return nil }
        self.quantity = quantity
      }
    }
    if let twoSocks = CartItem(name: "sock", quantity: 2) {
      println("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
    }
    // prints "Item: sock, quantity: 2"

    if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
      println("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
    } else {
      println("Unable to initialize zero shirts")
    }
    // prints "Unable to initialize zero shirts"
    if let oneUnnamed = CartItem(name: "", quantity: 1) {
        println("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
    } else {
        println("Unable to initialize one unnamed product")
    }
    // prints "Unable to initialize one unnamed product"

 

13.5 覆盖Failable构造器 Overriding a Failable Initializer

你可以像其他构造器一样覆盖父类的Failable构造器。你甚至可以使用一个非Failable构造器覆盖父类的Failable构造器,但是这种情况下,子类构造器不能向上代理调用父类的Failable构造器,一个非Failable构造器永远不能调用Failable构造器。

    class Document {
      var name: String? //可以为non-empty string,或nil,但是不能为empty string,默认值为nil
      init() {}// this initializer creates a document with a nil name value
      init?(name: String) {// this initializer creates a document with a non-empty name value
        if name.isEmpty { return nil }
        self.name = name
      }
    }
    class AutomaticallyNamedDocument: Document {
      override init() {
        super.init()
        self.name = "[Untitled]"
      }
      override init(name: String) {
        super.init()
        if name.isEmpty {
          self.name = "[Untitled]"
        } else {
          self.name = name
        }
      }
    }

 

14. init!Failable构造器 The init! Failable Initializer

相对于使用Failable构造器(关键字:init?)创建一个Optional Instance,你也可以使用Failable构造器(关键字:init!)创建一个隐式自解包可选实例 implicitly unwrapped optional instance 。

 

15. Required构造器 Required Initializers

通过 required 关键字表明一个类的构造器必须在它的每个子类中实现。

    class SomeClass {
      required init() {
        // initializer implementation goes here
     }
    }

子类也可以写上 required 关键字,表明这个子类的子类也必须实现这个构造器,可以不写override关键字。

    class SomeSubclass: SomeClass {
      required init() {
        // subclass implementation of the required initializer goes here
      }
    }

 

16. 通过闭包给属性设置默认值 Setting a Default Property Value with a Closure or Function

如果stored property的默认值需要自定义实现,可以闭包它设置默认值。

    class SomeClass {
      let someProperty: SomeType = {
        // create a default value for someProperty inside this closure
        // someValue must be of the same type as SomeType
        return someValue
      }()
    }

闭包结尾的挂号表明闭包会立即执行。如果不写挂号,则是表明你是把闭包本身赋值给属性,而不是闭包的返回值。

注意:上面的例子汇总,闭包执行的时候,实例的其他部分还没有被初始化,你不能再闭包中访问实例的其他属性,你不能使用self,不能调用实例的方法。

 

参考:https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html#//apple_ref/doc/uid/TP40014097-CH18-ID203

http://c.biancheng.net/cpp/html/2431.html

posted @ 2015-01-19 11:37  action爱生活  阅读(640)  评论(0编辑  收藏  举报