Swift 3中的初始化剖析

具有编程经验的软件开发人员应了解什么是“初始化”。 简而言之,这是在使用类或结构的实例之前涉及的一个过程。 在Swift中,这同样适用于枚举。

我浏览了有关初始化的Apple文档,https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html,并意识到Swift中有几个主要且有趣的功能值得分享,这最终促使我撰写了一篇主题相似的文章,但提供了有关每个主要功能的工作方式的更多示例。 但是,本文没有介绍与枚举有关的初始化概念。

本文旨在为我自己的学习提供参考,并希望与其他Swift开发人员分享“ Swift Initialization”。

在整个文章中,请始终阅读嵌入式屏幕截图中的绿色注释。

默认初始化程序和成员初始化程序

默认的初始值设定项是由类和结构自动引入的初始值设定项。 逐成员初始化器仅适用于结构,并且是默认初始化器的另一种形式。

  • 默认初始化程序在类和结构之间的工作略有不同。 如果未定义自定义初始化 程序,则结构类型会自动提供成员初始化 程序 。 类类型没有。
  • 成员级初始化程序采用所有存储的属性,并将其作为初始化程序的参数。
  • 不管存储的属性是否默认为初始值,都以结构类型提供成员初始化程序 。 但是,当存储的属性具有默认值和没有默认值时,它的行为会有所不同。

在结构中,如果存储的属性定义时默认使用值 ,并且提供自定义初始化程序,则结构会自动引入两个初始化程序,即不带参数的默认初始化程序逐成员初始化程序

但是,如果存储的属性在定义时没有默认值,则引入成员初始化器。 在下面的示例中, let car1 = Car()将触发编译异常。

自定义初始化

自定义初始化程序是开发人员在Swift中创建类或结构时定义的初始化程序。

  • 定义自定义初始化程序后,将没有默认的参数初始化程序逐成员初始化程序 (如果它是结构)。
  • 但是,如果我们希望使用默认的初始化器成员初始化器以及我们自己的自定义初始化器来初始化结构,则可以在extension中编写自定义初始化器
    请参阅以下部分以获取更多信息。

通过Swift扩展自定义初始化程序

扩展可以向现有类型添加新的便捷初始化程序 ,但不能指定初始值设定项

structure中 ,如果存储的属性定义时默认为值 ,并且提供自定义初始化程序,则结构会自动引入两个初始化程序, 默认为无参数初始化程序逐成员初始化程序,我们可以从中调用这两个初始化程序中的任何一个在扩展程序的初始化程序中。

初始化参数和参数标签

  • 初始化参数可以具有用于初始化程序主体的参数名称 ,以及用于调用初始化程序时的可选 参数标签
  • 但是,在定义初始值设定项的参数时使用_(下划线)符号时, 参数标签是可选的。
  • 下面的代码显示了两个带有第二个初始化程序的初始化程序,这些初始化程序无需调用参数标签即可调用该初始化程序。

值类型的初始化程序委托(结构,枚举)

初始化程序委派是一个初始化程序调用同一结构或类中或直到超类初始化程序的其他初始化程序的过程。

初始化程序委托在结构上比在类中更直接,因为结构不支持继承,并且委托只能在提供初始化程序的结构类型内发生。

而是,类支持继承,并且初始化程序委托除了提供初始化程序的类之外,还涉及超类(父级)初始化程序委托。

对于structure ,我们使用self.init来引用同一结构中的其他初始化程序。 但是,对于一个初始化程序而言,将调用委派给同一结构内的其他初始化程序是可选的。 我们只能在初始化程序中调用self.init

初始化期间的常量属性

我们可以在任何初始化程序中将值赋给常量变量。 但是,如果constant属性定义为默认值,则无法使用初始化程序中的其他值对其进行初始化。

类继承和初始化

由于支持类类型的继承 ,因此与值类型 (例如结构枚举)相比,初始化过程要复杂得多。 它遵循两阶段初始化过程,以下各节将详细说明。

指定的初始化程序和便利性初始化程序

类型中有两种类型的初始化器,即指定的初始化器便捷的初始化器

指定的初始值设定项是类中的主要初始值设定项,负责调用超类初始值设定项以继续初始化过程直至​​超类链。

指定的初始化程序的主体内它只能调用超类初始化程序,而不能在同一类(提供初始化程序)中调用便捷初始化 程序 。 一个类可以具有多个指定的初始化程序。

以下是指定的初始化程序的语法。

 初始化(参数){ 
陈述
}

便利初始化程序是一个类中的辅助初始化程序,并且只能在提供该初始值设定项的同一类中调用其他指定便利初始化程序。 它不能调用超类的初始化器

一个类也可以具有多个便捷初始化程序。 但是,便捷初始化器在类中是可选的。

以下是便捷初始化程序的语法, 它需要关键字 便捷

 便捷初始化(参数){ 
陈述
}

类类型的初始化程序委托规则

总之,初始化程序之间存在三个委托调用规则。

  1. 指定的初始值设定项 必须从其直接超类调用指定的初始值设定项 。 它不能打电话给其他人 指定的初始化器 在其中定义的任何 便利的初始化器
  2. 便捷初始化程序必须从同一类调用另一个初始化程序(指定的或便捷的)
  3. 便捷初始化程序必须最终调用指定的初始化程序

指定的便捷初始化器

本节显示了一个应用程序的更具体和更全面的示例,该应用程序实现了指定的初始化程序,便捷初始化程序以及自动初始化程序。

  1. DreamHome类提供了一个指定的初始化器和一个便捷的初始化器。

2. EhsanRiaHome类提供了一个指定的初始化程序和一个便捷的初始化程序。 便捷初始化程序将覆盖超类DreamHome指定的初始化程序。

3.因为DreamHome超类中所有指定的初始化器都已被EhsanRiaHome子类覆盖, EhsanRiaHome子类会自动继承超类中的所有便捷初始化器
屏幕快照中的intellisense清楚地表明, EhsanRiaHome子类提供了一个指定的初始化程序, EhsanRiaHome类中的一个重写的便捷初始化程序以及DreamHome超类的便捷初始化DreamHome (下面的屏幕快照中的第三个初始化程序)。

EhsanRiaHome继承的SmallerEhsanRiaHome类不提供任何自定义类,并且其所有存储的属性均使用默认值定义。 因此,它会自动从其超类继承所有指定的初始化器和便捷初始化器。

初始化程序的继承和覆盖

子类默认情况下不继承其超类初始化程序 。 但是,超类初始化某些情况下 继承的,我们将在下一节中详细讨论。

当我们编写与超类指定的 初始值设定项匹配的子类初始值设定项(指定或便利)时,我们必须在子类的初始值设定项定义之前编写override修饰符。 当它覆盖超类中自动提供的默认初始化程序时,将应用相同的规则。

相反,当我们编写与超类便捷性初始化程序匹配的子类初始化程序时 ,我们不必使用override修饰符。

自动初始化程序继承

如上所述,在某些情况下,子类会自动从超类初始化程序继承。

假定您为子类中引入的所有新存储属性提供默认值 ,则当满足以下条件时,子类会自动继承超类初始化器。

条件1

如果您的子类没有定义任何指定的初始值设定项 ,它将自动继承其所有超类指定的初始值设定项。
例如,在以下示例中,当在子类中没有定义任何指定的初始化器时, FirstHome子类会自动继承所有超类Home的指定的初始化器。

条件2

如果子类通过按条件1 自动继承超类初始化器或通过提供自定义实现继承来实现其所有超类指定的初始化器,则它会自动继承所有超类便利性初始化器。

子类可以通过子类便利性初始化器实现指定超类初始化 器,以满足此条件。

即使子类添加了其他便利初始化程序,该规则也适用。

两阶段初始化

类初始化是一个分为两个阶段的过程。

第一阶段 ,每个存储的属性都由引入它的类在初始化器的主体中分配一个初始值。

第二阶段 ,每个类都有机会在认为新实例可以使用之前进一步自定义其存储的属性。

Swift的编译器执行四个有用的安全检查,以确保两阶段初始化完成且没有错误。 该信息摘自Apple文档,但包含更多详细信息。

安全检查1

指定的初始值设定项必须确保由其类引入的所有属性在委托给超类初始值设定项链之前被初始化。

安全检查2

在将值分配给继承的属性之前,指定的初始值设定项必须委托一个超类初始值设定项。 如果不是这样,则指定的初始化器分配的新值将被超类覆盖,作为其自身初始化的一部分。

例如,仅当指定的初始值设定项通过super.init(age: 10).调用超类初始值设定项时,才能为继承的属性age赋值super.init(age: 10).

安全检查3

便捷初始化程序必须在将值分配给任何属性(包括由同一类定义的属性)之前委托给另一个初始化程序。 如果不是,便利初始化程序分配的新值将被其自己类的指定初始化程序覆盖。

安全检查4

在初始化的第一阶段完成之前,初始化器无法调用任何实例方法,读取任何实例属性的值或将self称为值。

阶段1

  • 指定的或便捷的初始化程序在类上调用。
  • 该类的指定初始化程序确认该类引入的所有存储属性都具有值。
  • 然后,指定的初始化程序将调用委派给超类初始化程序,以对其自己的存储属性执行相同的任务。
  • 这将继续在类继承链上进行操作,直到到达链的顶部,并且链中的最后一个类为其所有存储的属性分配了值。
  • 一旦超类的所有属性都具有初始值,并且阶段1完成。

阶段2

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

初始化失败

当我们知道初始化过程可能失败时,可以使用失败的初始化程序。 可以通过不满足某些条件的初始化器参数值,缺少某些依赖资源或某些其他阻止初始化成功的条件来触发失败。

我们无法使用相同的参数类型和名称来定义可失败的初始化程序和不可失败的初始化程序。

失败的初始化程序会创建引入失败的初始化程序的类型的可选值 。 我们在失败的初始化器中使用return nil来指示初始化失败时的一点。 但是,请注意,我们从不使用return关键字来指示初始化成功。 我们用init? 指示失败的初始化程序的关键字。

在下面的示例中,如果在创建Car类型的实例时传递给初始化程序的name属性的值为空,则它将触发初始化失败并返回nil。

初始化失败传播

一个失败的初始化器可以将调用委派给相同类或结构中的另一个失败的初始化器。 子类故障初始化器可以将调用委派给超类故障初始化器。

委托链中的任何初始化程序都会失败,整个初始化都会失败。

下面的屏幕快照显示了成功初始化的场景和使用两个类的初始化失败的两种场景,如上面的屏幕快照所示。

覆盖失败的初始化程序

我们可以从子类可失败的初始值设定项以及子类不可失败的初始值设定项覆盖 类可失败的初始值设定项

在下面的示例中,第327行中, override init(name: String)非初始化程序将覆盖超类可失败的初始化程序。
另外,请注意,从第330行到第334行,它显示了当从子类不可失败的初始化程序覆盖可失败的初始化程序时,在不可失败的初始化程序内,它无法将调用委派给可失败的初始化程序( line 334)

但是,有趣的是,当从子类可失败的初始化程序中重写可失败的初始化程序时,在可失败的初始化程序内,它可以将调用委派给可失败的初始化程序和不可失败的初始化程序。 (以下屏幕截图仅显示子类。有关Home超类,请参阅上面的屏幕截图)

摘要

我希望本文能很好地了解Swift中初始化的工作方式。 我必须再次强调,Apple文档确实在Swift编程中确实提供了很好的信息,并且肯定也包括有关初始化的主题。