15. 构造过程

文章目录
  1. 1. 设置存储属性的初始值
    1. 1.1. 构造器
    2. 1.2. 默认属性值
  2. 2. 自定义构造过程
    1. 2.1. 可选属性类型
    2. 2.2. 在构造过程期间给常量赋值
  3. 3. 默认构造器
  4. 4. 结构体类型的成员构造器
  5. 5. 值类型的构造器代理
  6. 6. 类的继承和构造过程
    1. 6.1. 指定构造器和便利构造器
    2. 6.2. 类的构造代理
    3. 6.3. 两段式构造器过程
  7. 7. 可失败构造器
    1. 7.1. 枚举的可失败构造器
    2. 7.2. 带有原始值枚举的可失败构造器
    3. 7.3. 构造失败的传递
    4. 7.4. 重写可失败构造器
    5. 7.5. init! 可失败构造器
  8. 8. 必要构造器
  9. 9. 使用闭包或函数设置默认属性值
  1. Setting Initial Values for Stored Properties(设置存储属性的初始值)
  2. Customizing Initialization
  3. Default Initializers
  4. Initializer Delegation for Value Types
  5. Class Inheritance and Initialization
  6. Failable Initializers
  7. Required Initializers
  8. Setting a Default Property Value with a Closure or Function

Swift 的构造器不用返回值。它们主要的作用就是确保在第一次使用前某类型 的实例都能正确的初始化。

设置存储属性的初始值

类和结构体在其创建实例时必须为它们所有的存储属性设置适当的初始值。存储属性不能处于未知状态。
你可以在构造器中为存储属性设置初始值,或是作为定义属性时的一部分设置其默认值。

注意:当你为存储属性设置默认值时,或是在构造器中设置其初始值,属性值是直接设置的,并不会调用任何属性观察器。

构造函数中改变计算属性,setter会调用, 存储属性的didset 不会调用. 亲自测试 。

原因分析:个人感觉观察器作用是观察变化,构造器设置是初始化操作,不是赋值操作,不算变化

构造器

构造器在创建某类实例时调用。其最简单的形式用 init 数的实例方法

默认属性值

你可以在构造器中设置一个存储属性的初始值。或指定一个 默认属性值 作为声明 属性的一部分。指定默认属性值你可以通过在属性定义时为其赋一个初始值。

注意:如果一个属性总是相同的初始值,与其在构造器中设置一个值不如提供一个默认值。其效果是相同的,但是默认值与属性构造器的联系更紧密一些。它使构造器更简短,更清晰,并且可以通过默认值推断属性类型。默认值也使你更易使用默认构造器和构造器继承,

如果属性的初始值总是相同,推荐使用默认值, 这样跟简洁,合理使用类型推断。

自定义构造过程

参数名:如果你在构造器中没有为每一个参数提供外部参数名,Swift 就会自动为其提供外部参数名。

注意如果不使用外部参数名是无法调用这些构造器的。如果在构造器中定义了外部参数名就必须使用,忽略它将会触发编译错误。

可选属性类型

可选类型的属性会自动被初始化为nil ,表示属性在构造过程期间故意设置为『没有 值』。

在构造过程期间给常量赋值

构造过程期间你可以在任何时间点给常量属性赋值,只要构造完成时设置了确定值即可。一旦常量属性被赋值,就不能再次修改。

注意:对于类的实例来说,常量属性只能在定义常量属性类的构造器中修改。不能在派生类中修改。

默认构造器

Swift 为属性均有默认值和没有构造器的结构体或类提供了一个默认构造器 。默认构造器创 建了一个所有属性都有默认值的新实例。

默认构造器的条件:所有存储属性都有默认值,且没有定义构造函数。

结构体类型的成员构造器

如果结构体没有任何自定义构造器,那么结构体类型会自动接收一个成员构造器。不同于默认构造器,即使结构体的存储属性没有默认值,它也会接收成员构造器。

逐一成员构造器的条件:没有定义构造函数。 比默认构造器少了一个默认值的条件

值类型的构造器代理

构造器可以调用其他构造器来执行实例的部分构造过程。这个过程称之为 构造器代理 ,以避免多个构造器之间的重复代码

对于值类型,在自定义构造器中使用 self.init 来引用同一类型中的其他构造器。你只能在构造器中调用 self.init

如果你为值类型定义了一个自定义构造器,你将无法再访问该类型的默认构造器( 如果是结构体就是成员构造器 )

> 注意:如果你想让你的自定义类型可以使用默认构造器,成员构造器,自定义构造器来进行初始化,就把自定义构造器写在扩展中,而不是作为值类型原始实现的一部分。

类的继承和构造过程

类的所有存储属性 — 包括任何从父类继承而来的属性 — 必须在构造过程期间赋值。
Swift 给类类型定义了两种构造器以确保所有存储属性都能接收到初始值。它们分别是指定构造器和便利构造器。

指定构造器和便利构造器

指定构造器是类的主要构造器。一个指定构造器初始化该类引入的所有属性,并调用合适的父类构造器以继续父类链上的构造过程。

便利构造器有着相同风格的写法,但是在 init 关键字之前需要放置 convenience 修饰符,并使用空格来分隔。

类的构造代理

为了简化指定构造器和便利构造器之间的关系。Swift 对构造器之间的代理采用了如下三条规则:

  1. 指定构造器必须调用其直系父类的指定构造器。
  2. 便利构造器必须调用同一类中的其他构造器。
  3. 便利构造器最后必须调用指定构造器。

简单的记忆方法:



1. 指定构造器必须 向上 代理。
2. 便利构造器必须 横向 代理。

两段式构造器过程

Swift 中类的构造过程是两段式处理。第一阶段,为类引入的每个存储属性赋一个初始值。一旦确定了所有存储属性的初始状态,第二阶段开始,在新的实例被认为可以使用前,每个类都有机会进一步定制其存储属性。



Swift 的编译器执行了四个有帮助的安全检查以确保两段式构造过程无误完成:

1. 安全检查 1:指定构造器必须确保其类引入的所有属性在向上代理父类构造器之前完成初始化。如上所述,一个对象的内存只在其所有存储属性初始状态已知时才被认为完全初始化。为了符合此规则,指定构造器必须确保其所属类拥有的属性在向上代理前完成初始化。
2. 安全检查 2:指定构造器必须在继承属性赋值前向上代理父类构造器,否则,便利构造器赋予的新值将被父类构造过程的一部分重写。
3. 安全检查 3:便利构造器必须在任何属性(包括同一类中定义的属性)赋值前代理另一个构造器。否则便利构造器赋予的新值将被其所属类的指定构造器重写。
4. 安全检查 4:构造器在第一阶段构造过程完成前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用 self 作为一个值。


以下是基于以上四个安全检查的两段式构造过程的流程:

阶段 1:

1. 在类中调用指定或便利构造器。
2. 对一个新实例分配内存,但内存没还没有初始化。 指定构造器确认其所属类的所有存储属性都有值。现在那些存储属性的内存初始化完成。
3. 指定构造器移交给父类构造器以为其存储属性执行相同的任务。
4. 这个过程沿着类的继承链持续向上,直到到达继承链的顶端。
5. 一旦到达链的顶端,并且链中最后的类确保其所有存储属性都有值,则认为实例的内 存已经完全初始化,至此阶段 1 完成。

阶段 2:

1. 从链顶端往下,链中每个指定构造器都可以选择进一步定制实例,构造器现在可以访问 self 并修改它的属性,调用实例方法,等等。
2. 最终,在链中的任何便利构造器也都可以选择定制实例以及使用 self 。


### 构造器的继承和重写

与 Objective-C 的派生类不同,Swift 的派生类默认不继承其父类构造器。Swift 这种机制防止了更定制化的派生类继承父类的简单构造器,也防止将简单构造器用于创建不完全初始化或是错误初始化的派生类实例。

当你在写一个与父类 指定构造器相匹配的派生类构造器时,你是在有效的重写指定构造器。 因此,你必须在派生类构造器的定义前写上修饰符 override 。即使你重写的是一个自动提供的默认构造器,也要写上 override 。

相反的,如果你写一个与父类便利构造器相匹配的派生类构造器,根据类的构造器代理规则,派生类是不能直接调用父类便利构造器的。因此,你的派生类(严格来说)没有重写父类构造器。所以,在提供与父类便利构造器相匹配的实现时,无需编写修饰
符 override


默认构造器(在可用时)总是类中的指定构造器。

派生类可在构造过程期间可修改变量继承属性,但不能修改常量继承属性。

### 自动构造器的继承

如上所述,派生类默认不继承其父类构造器。然而,如果满足某些特定条件,父类构造器是可以被自动继承的。实际上,这意味着很多常见场景中你不需要重写构造器,并且可以安全的以最小代价继承父类构造器。
假设你为派生类引入的所有属性提供了默认值,请应用以下两条规则以达到自动继承的目的:

1. 规则 1:如果你的派生类没有定义任何指定构造器,它会自动继承其父类的所有指定构造器。
2. 规则 2:如果你的派生类为其父类的所有指定构造器都提供了实现 — 无论是按照规则 1 继承而来, 或是定义时提供了自定义实现 — 它都会自动继承父类的所有便利构造器。甚至在派生类进一步添加便利构造器时,这些规则仍然适用。


自动继承的条件和默认构造器的条件是多么的相似,有默认值,没有定义指定构造函数 。

可失败构造器

为了应对可能失败的构造过程,你可以为类、结构体,或是枚举定义一个或是多个可失败构造器。编写可失败构造器的语法就是在 init 关键字后面添加问号( init? )。

注意:你不能使用相同的参数类型或参数名定义一个可失败构造器后又定义一个非失败构造器。

可失败构造器会创建一个关联值类型是自身构造类型的可选类型。在可失败构造器中编写 return nil 以表示可以在任何情况下触发失败。

注意:严格来说,构造器没有返回值。它们的作用是确保构造过程结束时 self 可以完全并正确的初始化。虽然你写 return nil 是用于触发构造器失败,但表示构造成功是不会使用 return 关键字的。

枚举的可失败构造器

带有原始值枚举的可失败构造器

构造失败的传递

类、结构体或枚举的可失败构造器可以横向代理同一类型中的其他可失败构造器。类似的,派生类的可失败构造器可以向上代理其父类的可失败构造器。

注意:一个可失败构造可以代理一个非失败构造器。如果你需要添加一个可能失败的状态到现有构造过程,请使用这个办法,否则将会构造失败。

重写可失败构造器

你可以在派生来中重写父类的可失败构造器,就像其他构造器那样。或是你可以用派生类的 非失败 构造器重写父类的可失败构造器。这允许你定义一个不会构造失败的派生类,即使父 类的构造过程允许失败。

注意如果你使用非失败的派生类构造器重写了可失败的父类构造器,向上代理父类构造器的唯一的方法就是强制解包(force-unwrap)可失败父类构造器的结果。

你可以使用非失败构造器重写可失败构造器,但是反过来不行。

init! 可失败构造器

通常使用在关键字 init 后面放置问号( init? )的方式来定义一个可失败构造器, 用于创建一个适当的可选类型实例。你也可以定义一个可失败构造器,将其用于创建一个适 当的隐式解包可选类型的实例。为了定义这个可失败构造器,在关键字 init 后面用叹号 来替代问号( init! )。

必要构造器

在类构造器的定义前写修饰符 required 以指明该类的每个派生类必须实现此构造器。

在每个派生类实现必要构造器时也必须在构造器前面写修饰符 required ,以指明构造器要求应用于继承链中所有派生类。重写一个必要指定构造器时无需写修饰符 override 。

使用闭包或函数设置默认属性值

1
2
3
4
5
6
class SomeClass {
let someProperty: SomeType = {
// 在闭包中创建⼀一个带有默认值的 someProperty // someValue 的类型必须是 SomeType
return someValue
}()
}

注意那个闭包的结束是在大括号后面尾随一对空括号。这告诉 Swift 立即执行闭包。如果你 忽略了这对括号,则为试图将闭包本身赋值给属性,而不是闭包的返回值。

注意:如果你使用闭包初始化属性,记住在闭包执行时其他实例还未初始化。这意味着无法在闭包中访问其他属性值,即使那些属性有默认值。你也不能隐式使用 self 属性,或 是调用实例方法。