Swift 初始化器

2,053 阅读1分钟

前言

文档地址:docs.swift.org/swift-book/…

Swift 的初始化器的设计和 Objective-C 有很大的不同。 Objective-C默认为属性设置了初始值,而Swift则不会。需要显示地设置各个属性的值。 初始化的目的是为所有的属性提供一个初始值,确保所有的属性在使用的时候是有值的。

值类型的初始化

来看一个例子

struct Person {
	var name: String?
    var age: Int
}

对于值类型,如果不写任何自定义的初始化方法,系统会提供一个默认的始化器,这个默认的初始化器是使用所有属性名称作为参数名称:

init(name: String?, age: Int)

当为Person提供了自定义的初始化器之后,默认的初始化器将会消失,不可以被使用。

 ///这里提供了初始化方法,init(name: String?, age: Int)将不能被使用
 ///只能使用  init(fullName:String,age:Int) 进行初始化
 init(fullName: String?,age: Int) {
     self.name = fullName
     self.age = age
 }

某些情况对于参数是有严格限制的,参数不符合条件会导致初始化失败,会失败的初始化器在swift中使用init?关键字,并在失败时 return nil,例如:

 //这是一个可以失败的初始化器
 init?(fullName: String?,age: Int) {
      //年龄不能小于1岁
      if age < 1 { return nil }
      self.name = fullName
      self.age = age
 }

类的初始化

类的初始化比值类型的初始化要复杂一些,因为类可以继承。但是原则是不变的,就是要确保所有的属性都要被初始化。

类初始化有两种类型:Designated Initializers (指定初始化器)Convenience Initializers (便捷初始化器)

指定初始化器的作用是为类的所有属性提供初始化的方法 ,类可以有多个 指定初始化器。 便捷初始化器的作用是为类提供一个便捷的初始化的方法 , 类也可以有多个,便捷初始化器。

用文档中的例子:

class Food {
    var name: String
    ///指定初始化器
    init(name: String) {
        self.name = name
    }
    ///便捷初始化器
    ///这个初始化器很方便,不需要传递name参数
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

为什么要分出两种类型的初始化器呢? 这个跟类的初始化方法继承有关后面会讲。

指定初始化器 和 便捷初始化器 有固定的交互规则,如下图: alt

  • 1.子类的指定初始化器必须调用父类的初始化器
  • 2.便捷初始化器必须调用同一个类中的另一个初始化器
  • 3.便捷初始化器最终会调用指定初始化器

上面这三条规则非常重要,其指导了子类如何编写自定义的初始化程序来正确的初始化类

类的初始化器的继承

依据上面的规则,子类继承父类初始化方法时有如下规则:

  • 1.如果子类没有定义任何指定的初始化器,则自动继承所有父类指定初始化器
  • 2.如果子类子类提供了所有父类指定初始化器的实现(通过规则1继承或者自定义实现),则继承所有父类的便捷初始化方法

来看文档中的例子:

   class Food {
      var name: String

      init(name: String) {
          self.name = name
      }
      convenience init() {
          self.init(name: "[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)
      }
  }

这些初始化方法的交互图: alt

RecipeIngredient 实现了父类Food的指定初始化器 init(name: String),所以其自动继承了父类便捷初始化器init(),另外RecipeIngredient 实现了自己的指定只初始化器init(name:String, quantity: Int). 在init(name:String.quantity: Int)方法中先设置了子类特有属性quantity的值,然后调用了父类的指定初始化器init(name:).这里也是有讲究的,这关系到swift类初始化的安全检查过程。

初始化安全检查

Swift中的类初始化是一个分为两个阶段的过程。在第一阶段,每个存储的属性都由引入它的类分配一个初始值。一旦确定了每个存储属性的初始状态,便开始第二阶段,并且在认为新实例可以使用之前,每个类都有机会自定义其存储属性。 这两个阶段可以防止类在初始化过程中意外的将属性设置为其它值。 Swift执行的安全检查有四个:

  • 安全检查1: 子类的特有属性必须在调用父类的指定初始化方法之前设置初始值。
  • 安全检查2: 只有在调用了父类的指定初始化方法之后,子类才可以给父类的属性设置初始值。(否则父类属性的初始值将会被覆盖)
  • 安全检查3: 便捷初始化程序在调用另个初始化程序之后设置属性的值。(否则将会被另个初始化程序重载)
  • 安全检查4: 在self的第一个阶段完成后,初始化程序才能调用实例方法,读取属性的值。

初始化两个阶段的详细过程:

阶段1

  • 在类上调用了指定的或便捷初始化方法

  • 分配该类的新实例内存,但是内存还没有被初始化

  • 该类的指定初始化方法确认该类引入的所有存储属性都具有值。这些存储属性的内存现在已经被初始化。

  • 调用父类的指定初始化器,父类重复上面的过程,一直到达继承链的顶部

  • 一旦到达链的顶部,则实例的内存被视为完全初始化,阶段1已经完成

阶段2

  • 从链的顶部向下回溯,链中每个指定初始化程序都可以选择并且进一步的自定义实例。初始化程序现在可以访问self并且可以修改其属性,调用其实例方法。

  • 最后,链中的所有便捷初始化方法都可以自定义实例并使用self

必须的初始化器

必须的初始化器实际上已一种特殊的指定初始化器,要求子类必须实现(而不是使用默认的继承)。实现形式是在方法前加上required关键字。

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

总结:

1.swift初始器的目的是为所有属性设置正确的初始值。
2.类的初始化器有指定和便捷之分。有固定的调用规则。
3.类的初始化有两个阶段,在每个阶段中要进行安全检查。 第一个阶段沿着继承链向上调用指定初始化方法一直到顶部,第二个阶段是回溯到底部,实现子类的自定义属性,以及访问self和调用实例方法。