初始化是准备使用的类,结构或枚举实例的过程。此过程涉及为该实例上的每个存储属性设置初始值,并执行新实例准备使用之前所需的任何其他设置或初始化。
您可以通过定义初始值设定项来实现此初始化过程,初始值设定项类似于可以调用以创建特定类型新实例的特殊方法。与Objective-C初始值设定项不同,Swift初始值设定项不会返回值。它们的主要作用是确保类型的新实例在首次使用之前正确初始化。
类的实例还可以实现一个deinitializer,它在释放该类的实例之前执行任何自定义清除。
设置存储属性的初始值
类和结构必须在创建该类或结构的实例时将其所有存储的属性设置为适当的初始值。存储的属性不能处于不确定状态。
您可以在初始化程序中为存储的属性设置初始值,或通过将默认属性值分配为属性定义的一部分来设置初始值。
当您为存储的属性分配默认值,或在初始化程序中设置其初始值时,将直接设置该属性的值,而无需调用任何属性观察器。
默认属性值
您可以从初始化程序中设置存储属性的初始值,或者指定默认属性值作为属性声明的一部分。您可以通过在定义属性时为其分配初始值来指定默认属性值。
如果属性始终采用相同的初始值,请提供默认值,而不要在初始化程序中设置值。最终结果是相同的,但是默认值将属性的初始化与其声明紧密联系在一起。它使初始化程序更短,更清晰,并使您能够从其默认值推断属性的类型。默认值还使您更容易利用默认初始化程序和初始化程序继承。
自定义初始化
您可以使用输入参数和可选属性类型,或通过在初始化期间分配常量属性来自定义初始化过程。
struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit:Double) {
temperatureInCelsius= (fahrenheit-32.0)/1.8
}
init(fromKelvin kelvin:Double) {
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
参数名称和参数标签
与函数和方法参数一样,初始化参数可以具有在初始化程序的主体内使用的参数名称和在调用初始化程序时使用的参数标签。
但是,初始化函数在其括号前没有以函数和方法那样的方式标识函数的名称。因此,初始化器参数的名称和类型在确定应调用哪个初始化器中起着特别重要的作用。因此,如果不提供初始化功能,Swift会为初始化程序中的每个参数提供一个自动参数标签。
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
}
}
不带参数标签的初始化参数
如果您不想为初始化参数使用参数标签,请为该参数写下划线(_)而不是显式参数标签,以覆盖默认行为。
可选属性类型
如果您的自定义类型具有在逻辑上允许为“无值”的存储属性(可能是因为在初始化期间无法设置其值,或者是因为在以后的某个时候允许其无值),请使用可选类型。可选类型的属性将使用值自动初始化nil,表明该属性在初始化过程中故意具有“没有值”。
在初始化期间分配常量属性
您可以在初始化期间的任何时候为常量属性分配一个值,只要在初始化完成时将其设置为确定值即可。为常数属性分配值后,就无法再对其进行修改。
对于类实例,只能在引入常量的类的初始化期间对其进行修改。子类不能修改它。
默认初始化器
迅速提供了一个默认初始值对于所有其属性提供缺省值,并且不提供至少一个初始值设定本身的任何结构或类。默认初始化程序仅创建一个新实例,并将其所有属性设置为其默认值。
类继承和初始化
在初始化期间,必须为类的所有存储属性(包括该类从其超类继承的所有属性)分配一个初始值。
Swift为类类型定义了两种初始化器,以帮助确保所有存储的属性都接收初始值。这些被称为指定的初始化程序和便捷初始化程序。
指定的初始化程序和便利性初始化程序
指定的初始化器是类的主要初始化器。指定的初始化程序将完全初始化该类引入的所有属性,并调用适当的超类初始化程序以继续超类链中的初始化过程。
类往往只有很少的指定初始化程序,而一个类只有一个很常见。指定的初始化程序是“漏斗”点,通过它们进行初始化,并通过该“漏斗”点,初始化过程在超类链中继续进行。
规则1
指定的初始化程序必须从其直接超类调用指定的初始化程序。
规则二
便捷初始化程序必须从同一类调用另一个初始化程序。
规则三
便捷初始化程序必须最终调用指定的初始化程序。
在这里,超类具有一个指定的初始值设定项和两个便利的初始值设定项。一个便利初始化程序调用另一个便利初始化程序,后者又调用单个指定的初始化程序。这从上方满足规则2和3。超类本身没有其他超类,因此规则1不适用。
该图中的子类具有两个指定的初始化程序和一个便捷的初始化程序。便捷初始化程序必须调用两个指定的初始化程序之一,因为它只能调用同一类中的另一个初始化程序。这从上方满足规则2和3。两个指定的初始值设定项都必须从超类调用单个指定的初始值设定项,才能满足上面的规则1。
二段式初始化
Swift中的类初始化是一个分为两个阶段的过程。在第一阶段,每个存储的属性都由引入它的类分配一个初始值。一旦确定了每个存储属性的初始状态,便开始第二阶段,并且在认为新实例可以使用之前,每个类都有机会自定义其存储属性。
Swift的两阶段初始化过程类似于Objective-C中的初始化。主要区别在于,在阶段1中,Objective-C为每个属性分配零或空值(例如0或nil)。Swift的初始化流程更加灵活,因为它可以让您设置自定义初始值,并且可以处理有效值0或nil无效值的类型。Swift的编译器执行四项有用的安全检查,以确保两阶段初始化完成且没有错误:
安全检查1
指定的初始值设定项必须确保由其类引入的所有属性在委托给超类初始值设定项之前都已初始化。
如上所述,仅在知道对象所有存储属性的初始状态后,才认为该对象的内存已完全初始化。为了满足此规则,指定的初始值设定项必须确保在传递链之前初始化其所有属性。
安全检查2
在将值分配给继承的属性之前,指定的初始值设定项必须委托一个超类初始值设定项。如果没有,则超类将为其指定的初始化程序分配的新值覆盖其自身的初始化。
安全检查3
便利初始化程序必须在将值分配给任何属性(包括由同一类定义的属性)之前委托给另一个初始化程序。如果不是,便利初始化程序分配的新值将被其自己类的指定初始化程序覆盖。
安全检查4
在初始化self的第一阶段完成之后,初始化程序才能调用任何实例方法,读取任何实例属性的值或将其称为值。
在第一阶段结束之前,该类实例并不完全有效。一旦在第一阶段结束时知道类实例是有效的,就只能访问属性,并且只能调用方法。
根据上述四个安全检查,以下是两阶段初始化如何进行:
阶段1
指定的或便捷的初始化程序在类上调用。
分配该类的新实例的内存。内存尚未初始化。
该类的指定初始化程序确认该类引入的所有存储属性都具有值。现在已初始化这些存储属性的内存。
指定的初始值设定项移交给超类初始值设定项,以为其自身的存储属性执行相同的任务。
这将继续类继承链,直到到达链的顶部。
一旦到达链的顶部,并且链中的最后一个类已确保其所有存储的属性都具有值,则实例的内存被视为已完全初始化,并且阶段1已完成。
阶段2
从链的顶部向下追溯,链中的每个指定的初始化程序都可以选择进一步自定义实例。初始化程序现在可以访问self并可以修改其属性,调用其实例方法,等等。
最后,链中的所有便利初始化程序都可以选择自定义实例并使用self。
初始化程序的继承和覆盖
与Objective-C中的子类不同,Swift子类默认情况下不继承其超类初始化器。Swift的方法可防止出现以下情况:超类中的简单初始化程序被更专门的子类继承,并用于创建未完全或正确初始化的子类的新实例。如果希望自定义子类提供与其父类相同的一个或多个相同的初始化器,则可以在子类中提供这些初始化器的自定义实现。当编写与超类指定的初始值设定项匹配的子类初始值设定项时,实际上是在提供该指定的初始值设定项的替代。因此,必须override在子类的初始化程序定义之前编写修饰符。即使您要覆盖自动提供的默认初始化程序,这也是正确的。
与覆盖属性,方法或下标一样,override修饰符的存在会提示Swift检查超类是否具有匹配的指定初始化器要被覆盖,并验证是否已按预期指定了覆盖初始化器的参数。override重写超类指定的初始值设定项时,您始终会编写修饰符,即使您的子类对初始值设定项的实现是便利的初始值设定项也是如此。
相反,如果您编写与超类便捷性初始化程序匹配的子类初始值设定项,您的子类将永远无法直接调用该超类便利性初始化程序。因此,您的子类(严格地说)没有提供超类初始值设定项的替代。因此,override在提供超类便捷初始化程序的匹配实现时,您无需编写修饰符。
自动初始化程序继承
如上所述,子类默认情况下不继承其超类初始化器。但是,如果满足某些条件,则会自动继承超类初始化器。实际上,这意味着您无需在许多常见情况下编写初始化程序覆盖,并且可以在安全的情况下以最小的努力继承超类初始化程序。
假设您为子类中引入的任何新属性提供默认值,则适用以下两个规则:
规则一
如果您的子类未定义任何指定的初始值设定项,它将自动继承其所有超类指定的初始值设定项。
规则二
如果您的子类提供了其所有超类指定初始化器的实现(通过按规则1继承它们,或通过提供自定义实现作为其定义的一部分),那么它将自动继承所有超类便利性初始化器。
即使您的子类添加了进一步的便利初始化程序,这些规则也适用。