初始化是准备类、结构或枚举的实例以供使用的过程。此过程涉及为该实例上的每个存储属性设置初始值,并执行新实例准备使用之前需要的任何其他设置或初始化。
您可以通过定义初始化器来实现此初始化过程,初始化器类似于可调用的特殊方法,用于创建特定类型的新实例。与Objective-C初始化器不同,Swift初始化器不返回值。它们的主要作用是确保类型的新实例在第一次使用之前被正确初始化。
类类型的实例还可以实现反初始化器deinitializer,它在类的实例被释放之前执行任何自定义清理。
Setting Initial Values for Stored Properties 设置存储属性的初始值
类和结构必须在创建该类或结构的实例时将其所有存储属性设置为适当的初始值。存储的属性不能处于不确定状态。
您可以在初始化器中为存储的属性设置初始值,或者通过将默认属性值指定为属性定义的一部分来实现。下面几节将描述这些操作。
注意:当为存储的属性分配默认值,或在初始化器中设置其初始值时,将直接设置该属性的值,而不调用任何属性观察者。
Initializers 初始化
调用初始化器来创建特定类型的新实例。在最简单的形式中,初始化器就像一个没有参数的实例方法,使用init关键字编写:
init() {
// perform some initialization here
}
下面的示例定义了一个名为华氏度的新结构,用于存储以华氏温标表示的温度。华氏结构有一个存储特性,温度,类型为Double:
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit"
该结构定义了一个没有参数的初始化器init,它初始化存储的温度,其值为32.0(水的冰点,以华氏度为单位)。
Default Property Values 默认的属性值
可以从初始化器中设置存储属性的初始值,如上所示。或者,指定一个默认属性值作为属性声明的一部分。通过在定义属性时为其分配初始值,可以指定默认属性值。
如果属性总是使用相同的初始值,则提供默认值,而不是在初始化器中设置值。最终结果是相同的,但是默认值将属性的初始化与其声明更紧密地联系在一起。它使初始化器更短、更清晰,并使您能够从属性的默认值推断属性的类型。缺省值还使您更容易利用缺省初始化器和初始化器继承,本章稍后将对此进行描述。
您可以从上面以更简单的形式编写华氏结构,方法是在属性声明时为其温度属性提供一个默认值:
struct Fahrenheit {
var temperature = 32.0
}
Customizing Initialization 自定义初始化
您可以使用输入参数和可选属性类型自定义初始化过程,或者在初始化期间分配常量属性,如下面的部分所述。
Initialization Parameters 初始化参数
您可以提供初始化参数作为初始化器定义的一部分,以定义自定义初始化过程的值的类型和名称。初始化参数具有与函数和方法参数相同的功能和语法。
下面的示例定义了一个名为摄氏度的结构,它存储以摄氏度表示的温度。摄氏度结构实现了两个自定义初始化器init(from fahrenheit:)和init(from kelvin:),这两个初始化器使用来自不同温标的值初始化结构的一个新实例:
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)
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
第一个初始化器有一个初始化参数,参数标签为fromFahrenheit,参数名称为fahrenheit。第二个初始化器有一个初始化参数,参数标签为fromKelvin,参数名称为kelvin。两个初始化器都将它们的单个参数转换为相应的摄氏度值,并将该值存储在一个名为temperatureInCelsius的属性中。
Parameter Names and Argument Labels 参数名称和参数标签
与函数和方法参数一样,初始化参数可以在初始化器的主体中使用参数名称,也可以在调用初始化器时使用参数标签。
然而,初始化器在圆括号前没有函数和方法那样的标识函数名。因此,初始化器参数的名称和类型在确定应该调用哪个初始化器时起着特别重要的作用。因此,如果不提供初始化器中的每个参数,Swift将为它们提供一个自动参数标签。
下面的示例定义了一个名为Color的结构,它具有三个常量属性,分别称为红色、绿色和蓝色。这些属性存储一个0.0到1.0之间的值,表示颜色中红色、绿色和蓝色的数量。
Color为其红色、绿色和蓝色组件提供了一个初始化器,该初始化器具有三个类型为Double的适当命名的参数。Color还提供了第二个初始化器,该初始化器具有一个白色参数,用于为所有三个Color组件提供相同的值。
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
}
}
这两个初始化器都可以用来创建一个新的Color实例,方法是为每个初始化器参数提供命名值:
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 - argument labels are required
Initializer Parameters Without Argument Labels 没有参数标签的初始化参数
如果不希望为初始化器参数使用参数标签,请为该参数编写下划线(_),而不是显式参数标签,以覆盖默认行为。
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)
// bodyTemperature.temperatureInCelsius is 37.0
初始化器调用摄氏度(37.0)的意图很清楚,不需要参数标签。因此,将这个初始化器编写为init(_ celsius:Double)是合适的,这样就可以通过提供一个未命名的Double值来调用它。
Optional Property Types 可选的属性类型
如果您的自定义类型具有逻辑上允许具有“无值”的存储属性—可能是因为在初始化期间不能设置其值,或者因为在稍后的某个时刻允许具有“无值”—请使用可选类型声明该属性。可选类型的属性将自动初始化为nil值,这表明在初始化期间该属性有意设置为“暂无值”。
下面的例子定义了一个名为SurveyQuestion的类,它有一个可选的字符串属性response:
class SurveyQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
调查问题的响应只有在被询问时才能知道,因此响应属性是用字符串类型声明的?,或“可选字符串”。在初始化SurveyQuestion的新实例时,它会自动分配一个默认值nil,意思是“还没有字符串”。
Assigning Constant Properties During Initialization 在初始化期间分配常量属性
您可以在初始化期间的任何时刻为常量属性赋值,只要在初始化完成时将其设置为一个确定的值即可。一旦一个常量属性被赋值,它就不能被进一步修改。
注意:对于类实例,常量属性在初始化期间只能由引入它的类修改。它不能被子类修改。
您可以从上面修改SurveyQuestion示例,为问题的文本属性使用常量属性而不是变量属性,以表明一旦创建了SurveyQuestion实例,问题就不会更改。即使text属性现在是常量,它仍然可以在类的初始化器中设置:
class SurveyQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// Prints "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"
Default Initializers 默认构造器
Swift为任何结构或类提供默认初始化器,这些结构或类为其所有属性提供默认值,且本身不提供至少一个初始化器。默认初始化器只是创建一个新实例,并将其所有属性设置为默认值。
这个例子定义了一个名为ShoppingListItem的类,它封装了购物列表中商品的名称、数量和购买状态:
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
因为ShoppingListItem类的所有属性都有默认值,而且它是一个没有超类的基类,所以ShoppingListItem自动获得一个默认初始化器实现,该实现创建一个新实例,所有属性都设置为默认值。(name属性是一个可选的字符串属性,因此它自动接收一个默认值nil,即使这个值不是在代码中编写的。)上面的示例使用ShoppingListItem类的默认初始化器来创建该类的一个新实例,并使用初始化器语法编写为ShoppingListItem(),并将这个新实例分配给一个名为item的变量。
Memberwise Initializers for Structure Types 结构体类型的所有成员构造器
与默认构造器不同,无论结构体中存储的属性有没有默认值,都会有一个接收所有成员初始化器。
所有成员构造器是初始化新结构实例的成员属性的一种简写方法。新实例的属性的初始值可以通过名称传递给成员初始化器。
下面的示例定义了一个名为Size的结构,它具有两个名为width和height的属性。通过指定默认值0.0,可以推断这两个属性的类型都是Double。
Size结构自动接收一个init(width:height:)成员初始化器,您可以使用它初始化一个新的Size实例:
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
Initializer Delegation for Value Types 值类型的初始化器委托
初始化器可以调用其他初始化器来执行实例初始化的一部分。这个过程称为初始化器委托,它避免了跨多个初始化器复制代码。
对于值类型和类类型,初始化器委托如何工作以及允许使用何种形式的委托的规则是不同的。值类型(结构和枚举)不支持继承,因此它们的初始化器委托过程相对简单,因为它们只能委托给它们自己提供的另一个初始化器。但是,类可以从其他类继承,如继承中所述。这意味着类有额外的责任来确保它们继承的所有存储属性在初始化期间都分配了合适的值。下面的类继承和初始化描述了这些职责。
对于值类型,你可以在自定义构造器中调用self.init
注意,如果您为值类型定义了自定义初始化器,那么您将不再能够访问该类型的默认初始化器(如果它是一个结构,则为memberwise初始化器)。此约束可防止使用自动初始化器之一的人意外地绕过复杂初始化器中提供的其他必要设置。
注意:如果您希望您的自定义值类型可以使用缺省初始化器和memberwise初始化器进行初始化,也可以使用您自己的自定义初始化器进行初始化,请将自定义初始化器编写在扩展中,而不是作为值类型初始实现的一部分。有关更多信息,请参见 Extensions。
下面的示例定义了一个自定义矩形结构来表示几何矩形。这个例子需要两个支持结构Size和Point,它们都为所有属性提供0.0的默认值:
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
可以通过以下三种方法之一初始化下面的Rect结构:使用其默认的零初始化原点和大小属性值,提供特定的原点和大小,或者提供特定的中心点和大小。这些初始化选项由三个自定义初始化器表示,它们是Rect结构定义的一部分:
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)
}
}
let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)
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)
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
Class Inheritance and Initialization 类继承和初始化
在初始化过程中,必须为类的所有存储属性(包括该类从超类继承的任何属性)分配一个初始值。
Swift为类类型定义了两种初始化器,以帮助确保所有存储的属性都接收到初始值。这些被称为指定(designated)初始化器和便利(convenience )初始化器。
Designated Initializers and Convenience Initializers
指定初始化器和方便的初始化器
指定初始化器是类的主要初始化器。指定的初始化器将完全初始化该类引入的所有属性,并调用适当的超类初始化器,以便在超类链上继续初始化过程。
类往往只有很少的指定初始化器,而且一个类通常只有一个初始化器。指定的初始化器是进行初始化的“漏斗”点,初始化过程通过这些“漏斗”点在超类链上继续进行。
每个类必须至少有一个指定的初始化器。在某些情况下,可以通过从超类继承一个或多个指定的初始化器来满足此需求,如下面的自动初始化器继承中所述。
便利初始化器是次要的,支持类的初始化器。您可以定义一个便利初始化器,以便从与便利初始化器相同的类调用指定的初始化器,并将指定初始化器的一些参数设置为默认值。您还可以定义一个方便的初始化器,为特定的用例或输入值类型创建该类的实例。
如果类不需要初始化器,则不必提供方便的初始化器。当一个通用初始化模式的快捷方式可以节省时间或者使类的初始化更清晰时,创建方便的初始化器。
Syntax for Designated and Convenience Initializers 指定的和方便的初始化器的语法
类的指定初始化器与值类型的简单初始化器的编写方法相同:
init(parameters) {
statements
}
便利初始化器是用相同的风格编写的,但是在init关键字前加上 convenience 修饰符,并用空格隔开:
convenience init(parameters) {
statements
}
Initializer Delegation for Class Types
为了简化指定初始化器和便利初始化器之间的关系,Swift对初始化器之间的委托调用应用了以下三条规则:
规则1
指定初始化器必须从其直接超类调用指定初始化器。
规则2
一个方便的初始化器必须从同一个类调用另一个初始化器。
规则3
方便的初始化器最终必须调用指定的初始化器。
记住这一点的一个简单方法是:
- 指定的初始化程序必须始终向上委托。
- 便利初始化器必须始终跨委托。
这些规则如下图所示:
在这里,超类有一个指定的初始化器和两个方便的初始化器。一个方便的初始化器调用另一个方便的初始化器,后者反过来调用单个指定的初始化器。这满足上面的规则2和规则3。超类本身没有进一步的超类,因此规则1不适用。
图中的子类有两个指定的初始化器和一个方便的初始化器。便利初始化器必须调用两个指定初始化器中的一个,因为它只能从同一个类调用另一个初始化器。这满足上面的规则2和规则3。两个指定初始化器都必须从超类调用一个指定初始化器,以满足上面的规则1。
这些规则不影响类的用户如何创建每个类的实例。上图中的任何初始化器都可以用来创建它们所属类的完全初始化实例。规则只影响如何编写类的初始化器的实现。
下图显示了四个类的更复杂的类层次结构。它说明了这个层次结构中指定的初始化器如何作为类初始化的“漏斗”点,简化了链中类之间的相互关系:
Two-Phase Initialization 两阶段初始化
Swift中的类初始化是一个两阶段的过程。在第一个阶段中,每个存储的属性由引入它的类分配一个初始值。一旦确定了每个存储属性的初始状态,第二个阶段就开始了,每个类都有机会在新实例准备使用之前进一步定制其存储属性。
使用两阶段初始化过程使初始化安全,同时仍然为类层次结构中的每个类提供完全的灵活性。两阶段初始化可以防止属性值在初始化之前被访问,也可以防止属性值被另一个初始化器意外地设置为不同的值。
Swift的两阶段初始化过程类似于Objective-C中的初始化。主要的区别在于,在第1阶段,Objective-C为每个属性分配0或null值(比如0或nil)。Swift的初始化流更灵活,因为它允许您设置自定义初始值,并且可以处理0或nil不是有效默认值的类型。
Swift的编译器执行了四个有用的安全检查,以确保两阶段初始化没有错误地完成:
Safety check 1
指定的初始化器必须确保在将其委托给超类初始化器之前初始化其类引入的所有属性。
如上所述,只有在知道对象所有存储属性的初始状态之后,才会认为对象的内存已经完全初始化。为了满足这个规则,指定的初始化器必须确保在传递到链之前初始化了它自己的所有属性。
Safety check 2
指定的初始化器必须委托给超类初始化器,然后才给继承的属性赋值。如果没有,指定初始化器分配的新值将被超类覆盖,作为其初始化的一部分。
Safety check 3
在为任何属性(包括由相同类定义的属性)赋值之前,方便的初始化器必须委托给另一个初始化器。如果没有,则便捷性初始化器分配的新值将被该类的指定初始化器覆盖。
Safety check 4
在初始化的第一阶段完成之前,初始化器不能调用任何实例方法、读取任何实例属性的值或将self引用为值。
直到第一个阶段结束,类实例才完全有效。只有在知道类实例在第一阶段结束时有效时,才能访问属性和调用方法。
基于以上四项安全检查,以下是两阶段初始化的执行过程:
第一阶段
- 在类上调用指定的或方便的初始化器。
- 为该类的新实例分配内存。内存还没有初始化。
- 该类的指定初始化器确认该类引入的所有存储属性都有一个值。现在初始化这些存储属性的内存。
- 指定的初始化器传递给超类初始化器,以对其自身存储的属性执行相同的任务。
- 这将继续沿着类继承链向上,直到到达链的顶部。
- 一旦到达链的顶部,并且链中的最后一个类确保其所有存储的属性都有一个值,则认为实例的内存已经完全初始化,并且阶段1已经完成。
第二阶段
- 从链的顶部向下工作,链中的每个指定初始化器都有进一步定制实例的选项。初始化器现在能够访问self并修改它的属性、调用它的实例方法等等。
- 最后,链中的任何便利初始化器都有自定义实例和使用self的选项。
下面是第1阶段如何寻找一个假设子类和超类的初始化调用:
超类的指定初始化器现在有机会进一步定制实例(尽管不必这样做)。
一旦超类的指定初始化器完成,子类的指定初始化器就可以执行额外的自定义(尽管它也不必这样做)。
最后,一旦子类的指定初始化器完成,最初调用的便利初始化器就可以执行额外的定制。
Initializer Inheritance and Overriding 初始化器继承和覆盖
与Objective-C中的子类不同,Swift子类默认情况下不继承它们的超类初始化器。Swift的方法可以防止超类的简单初始化器被更专门化的子类继承,并用于创建未完全或正确初始化的子类的新实例。
超类初始化器在某些情况下是继承的,但只有在这样做是安全且合适的时候才会继承。
如果希望自定义子类显示与其超类相同的一个或多个初始化项,可以在子类中提供这些初始化项的自定义实现。
当您编写与超类指定的初始化器匹配的子类初始化器时,您实际上是在覆盖指定的初始化器。因此,必须在子类的初始化器定义之前编写覆盖修饰符。
与覆盖的属性、方法或下标一样,覆盖修饰符的存在提示Swift检查超类是否有匹配的指定初始化器要被覆盖,并验证覆盖初始化器的参数是否已按预期指定。
当重写超类指定的初始化器时,总是要编写覆盖修饰符,即使子类的初始化器实现是一个方便的初始化器。
相反,如果您编写的子类初始化器与超类便利初始化器匹配,则子类永远不能直接调用该超类便利初始化器,这与上面在类类型初始化器委托中描述的规则相同。因此,子类(严格地说)不提供超类初始化器的覆盖。因此,在提供超类便利初始化器的匹配实现时,不需要编写覆盖修饰符。
下面的示例定义了一个名为Vehicle的基类。这个基类声明了一个名为numberOfWheels的存储属性,默认Int值为0。numberOfWheels属性被一个名为description的计算属性用来创建车辆特性的字符串描述:
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
Vehicle类为其惟一存储的属性提供默认值,本身不提供任何自定义初始化器。因此,它自动接收默认初始化器,如默认初始化器中所述。默认初始化器(如果可用)始终是类的指定初始化器,可以用来创建一个新的Vehicle实例,其车轮数为0:
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
下一个例子定义了一个名为Bicycle的车辆子类:
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
自行车子类定义了一个定制的指定初始化器init()。这个指定初始化器与Bicycle超类中的指定初始化器匹配,因此此初始化器的自行车版本使用覆盖修饰符标记。
自行车的init()初始化器首先调用super.init(),它调用自行车类的超类Vehicle的默认初始化器。这确保了继承的属性numberOfWheels在bike有机会修改属性之前由Vehicle初始化。在调用super.init()之后,numberOfWheels的原始值被替换为一个新值2。
如果你创建了一个自行车实例,你可以调用它的继承描述计算属性来查看它的numberOfWheels属性是如何更新的:
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// Bicycle: 2 wheel(s)
如果子类初始化器在初始化过程的第2阶段没有执行自定义,而超类有一个指定为零参数的初始化器,则可以在为所有子类的存储属性赋值之后省略对super.init()的调用。
这个例子定义了另一个Vehicle的子类,称为Hoverboard。在初始化器中,悬浮板类只设置它的color属性。这个初始化器不是显式地调用super.init(),而是依赖于对其超类的初始化器的隐式调用来完成这个过程。
class Hoverboard: Vehicle {
var color: String
init(color: String) {
self.color = color
// super.init() implicitly called here
}
override var description: String {
return "\(super.description) in a beautiful \(color)"
}
}
悬浮滑板Hoverboard的实例使用车辆初始化器提供的默认车轮数。
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver
子类可以在初始化期间修改继承的变量属性,但不能修改继承的常量属性。
Automatic Initializer Inheritance 自动初始化继承
如上所述,子类默认情况下不继承它们的超类初始化器。但是,如果满足某些条件,超类初始化器将自动继承。在实践中,这意味着您不需要在许多常见的场景中编写初始化器覆盖,并且可以在安全的情况下以最小的努力继承超类初始化器。
假设您为您在子类中引入的任何新属性提供默认值,则应用以下两条规则:
规则1
如果子类没有定义任何指定的初始化器,它将自动继承其所有超类指定的初始化器。
规则2
如果子类提供了所有超类指定的初始化器的实现——要么按照规则1继承它们,要么作为定义的一部分提供自定义实现——那么它将自动继承所有超类便利初始化器。
即使子类添加了更方便的初始化器,这些规则仍然适用。
注意:作为满足规则2的一部分,子类可以实现超类指定的初始化器作为子类便利初始化器。
Designated and Convenience Initializers in Action 指定的和方便的初始化作用
下面的示例显示了指定的初始化器、方便的初始化器和正在运行的自动初始化器继承。这个例子定义了一个由三个类组成的层次结构,分别是Food、recipecomponent和ShoppingListItem,并演示了它们的初始化器是如何交互的。
层次结构中的基类称为Food,这是一个封装食物名称的简单类。Food类引入了一个名为name的字符串属性,并为创建Food实例提供了两个初始化器
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
下图显示了Food类的初始化器链:
类没有默认的成员初始化器,因此Food类提供了一个指定的初始化器,它只接受一个名为name的参数。该初始化器可用于创建一个具有特定名称的新食品实例:
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"
Food类中的init(name: String)初始化器作为指定的初始化器提供,因为它确保新Food实例的所有存储属性都已完全初始化。Food类没有超类,因此init(name: String)初始化器不需要调用super.init()来完成初始化。
Food类还提供了一个方便的初始化器init(),没有参数。init()初始化器为一个新食物提供了一个默认占位符名称,方法是将其委托给食物类的init(name: String),其名称值为[未命名]:
let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"
层次结构中的第二类是食物的子类RecipeIngredient。RecipeIngredient类为烹饪菜谱中的一个成分建模。它引入了一个名为quantity的Int属性(除了从食物中继承的name属性之外),并定义了两个初始化器来创建RecipeIngredient实例:
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)
}
}
下图显示了RecipeIngredient 类的初始化器链:
RecipeIngredient类有一个指定的初始化器init(名称:String,数量:Int),它可以用来填充新recipe配料实例的所有属性。这个初始化器首先将传递的quantity参数分配给quantity属性,这是RecipeIngredient引入的惟一一个新属性。这样做之后,初始化器将委托给Food类的初始化器(名称:String)。此过程满足上述两阶段初始化的安全检查1。
RecipeIngredient还定义了一个方便的初始化器init(name: String),它用于仅根据名称创建RecipeIngredient实例。这个方便的初始化器假设对于任何没有显式数量创建的RecipeIngredient实例,数量为1。这个便利初始化器的定义使RecipeIngredient实例的创建更加快捷和方便,并且在创建多个单数量的RecipeIngredient实例时避免了代码重复。这个方便的初始化器简单地委托给类的指定初始化器,传递一个量值1。
RecipeIngredient提供的init(name: String)便利初始化器使用与食物中的init(name: String)指定初始化器相同的参数。因为这个方便的初始化器从它的超类中覆盖指定的初始化器,所以必须用覆盖修饰符标记它(如初始化器继承和覆盖中描述的那样)。
尽管RecipeIngredient 提供init(name: String)初始化器作为一种方便的初始化器,但是RecipeIngredient 仍然提供了它的所有超类的指定初始化器的实现。因此,RecipeIngredient 也自动继承了它的超类的所有便利初始化器。
在这个例子中,RecipeIngredient的超类是Food,它有一个简单的初始化器init()。因此,RecipeIngredient继承了这个初始化器。init()函数的继承版本与食品版本的方法完全相同,只是它委托给init的RecipeIngredient版本(名称:String),而不是食品版本。
这三个初始化器都可以用来创建新的RecipeIngredient实例:
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
层次结构中的第三个也是最后一个类是RecipeIngredient的子类ShoppingListItem。ShoppingListItem类对出现在购物列表中的菜谱成分建模。
购物清单上的每一项都以“未购买”开始。为了表示这一事实,ShoppingListItem引入了一个名为purchase的布尔属性,默认值为false。ShoppingListItem还添加了一个computed description属性,它提供了一个ShoppingListItem实例的文本描述:
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
注意:ShoppingListItem没有定义初始化器来为已购买的商品提供初始值,因为购物列表中的商品(如这里所建模的)一开始总是未购买的。
因为ShoppingListItem为它所引入的所有属性提供了一个默认值,并且本身没有定义任何初始化器,所以ShoppingListItem自动地从它的超类继承所有指定的和方便的初始化器。
下图显示了所有三个类的整体初始化链:
您可以使用所有三个继承的初始化器来创建一个新的ShoppingListItem实例:
var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘
这里,一个名为早餐列表的新数组是由一个包含三个新的ShoppingListItem实例的数组文本创建的。数组的类型被推断为[ShoppingListItem]。创建数组后,数组开始处的ShoppingListItem名称将从“[未命名]”更改为“Orange juice”,并标记为已购买。打印数组中每一项的描述将显示它们的默认状态已按预期设置。
Failable Initializers 可失败的构造器
有时,定义初始化可能失败的类、结构或枚举是有用的。此故障可能由无效的初始化参数值、缺少所需的外部资源或其他阻止初始化成功的条件触发。
要处理可能失败的初始化条件,请将一个或多个可失败的初始化器定义为类、结构或枚举定义的一部分。通过在init关键字(init?)后面加上问号,可以编写一个失败的初始化器。
注意:不能使用相同的参数类型和名称定义可失败初始化器和不可失败初始化器。
可失败初始化器创建其初始化类型的可选值。在可失败初始化器中写入返回nil,以指示可以触发初始化失败的点。
严格地说,初始化器不返回值。相反,它们的作用是确保在初始化结束时完全正确地初始化了self。虽然您编写return nil来触发初始化失败,但是您没有使用return关键字来表示初始化成功。
例如,可失败初始化器用于数值类型转换。要确保数值类型之间的转换精确地维护值,请使用init(exactly:)初始化器。如果类型转换无法维护该值,则初始化器将失败。
let wholeNumber: Double = 12345.0
let pi = 3.14159
if let valueMaintained = Int(exactly: wholeNumber) {
print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}
// Prints "12345.0 conversion to Int maintains value of 12345"
let valueChanged = Int(exactly: pi)
// valueChanged is of type Int?, not Int
if valueChanged == nil {
print("\(pi) conversion to Int does not maintain value")
}
// Prints "3.14159 conversion to Int does not maintain value"
下面的示例定义了一个名为Animal的结构,它具有一个名为species的常量字符串属性。动物结构还定义了一个可失败的初始化器,该初始化器只有一个名为species的参数。此初始化器检查传递给初始化器的物种值是否为空字符串。如果找到空字符串,将触发初始化失败。否则,设置物种属性的值,初始化成功:
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
您可以使用这个失败初始化器来尝试初始化一个新的动物实例,并检查初始化是否成功:
let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not Animal
if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}
// Prints "An animal was initialized with a species of Giraffe"
如果将空字符串值传递给失败初始化器的物种参数,初始化器将触发初始化失败:
let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal
if anonymousCreature == nil {
print("The anonymous creature could not be initialized")
}
// Prints "The anonymous creature could not be initialized"
在上面的例子中,空字符串("")是一个有效的、非可选的字符串。但是,动物不能将空字符串作为其物种属性的值。要对这个限制进行建模,如果找到空字符串,可失败初始化器将触发初始化失败。
Failable Initializers for Enumerations 枚举可失败的构造器
可以使用可失败构造器根据一个或多个参数选择适当的枚举用例。如果提供的参数不匹配适当的枚举用例,初始化器可能会失败。
下面的示例定义了一个名为TemperatureUnit的枚举,它具有三种可能的状态(kelvin 开尔文, celsius摄氏, fahrenheit华氏)。可失败初始化程序用于为表示温度符号的字符值找到合适的枚举用例:
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
}
}
}
您可以使用这个失败初始化器为这三种可能的状态选择适当的枚举用例,如果参数不匹配其中一种状态,则会导致初始化失败:
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
print("This is a defined temperature unit, so initialization succeeded.")
}
//成功
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print("This is not a defined temperature unit, so initialization failed.")
}
//失败
Failable Initializers for Enumerations with Raw Values
用于具有原始值的枚举的可失败初始化器
带有原始值的枚举将自动接收一个可失败初始化器init?(rawValue:),该初始化器接受一个名为rawValue的参数,该参数具有适当的raw-value类型,如果找到匹配的枚举,则选择匹配的枚举用例;如果不存在匹配值,则触发初始化失败。
您可以重写上面的TemperatureUnit示例,使用类型为Character的原始值,并利用init?(rawValue:)初始化器:
enum TemperatureUnit: Character {
case kelvin = "K", celsius = "C", fahrenheit = "F"
}
let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
print("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 {
print("This is not a defined temperature unit, so initialization failed.")
}
// Prints "This is not a defined temperature unit, so initialization failed."
Propagation of Initialization Failure 初始化失败的传播
类、结构或枚举的可失败初始化器可以从相同的类、结构或枚举委托到另一个可失败初始化器。类似地,子类可失败初始化器可以委托给超类可失败初始化器。
在这两种情况下,如果您将委托给导致初始化失败的另一个初始化器,则整个初始化过程将立即失败,并且不会执行进一步的初始化代码。
可失败初始化器也可以委托给不可失败初始化器。如果需要将潜在的失败状态添加到现有的初始化过程中,否则不会失败,请使用此方法。
下面的示例定义了一个名为CartItem的产品子类。CartItem类为在线购物车中的项目建模。CartItem引入了一个名为quantity的存储常量属性,并确保该属性的值始终至少为1:
class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
if quantity < 1 { return nil }
self.quantity = quantity
super.init(name: name)
}
}
CartItem的故障初始化器首先验证它接收到的数量值为1或更多。如果数量无效,则整个初始化过程将立即失败,并且不再执行进一步的初始化代码。同样,Product的failable初始化器检查name值,如果name是空字符串,初始化器进程将立即失败。
如果使用非空名称和数量为1或更多的CartItem实例,初始化成功:
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// Prints "Item: sock, quantity: 2"
如果您试图创建一个数量值为0的CartItem实例,CartItem初始化器会导致初始化失败:
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"
类似地,如果您试图创建一个具有空name值的CartItem实例,超类Product初始化器会导致初始化失败:
if let oneUnnamed = CartItem(name: "", quantity: 1) {
print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product"
Overriding a Failable Initializer 覆盖可失败的构造器
您可以像覆盖任何其他初始化器一样,覆盖子类中的超类故障初始化器。或者,您可以使用子类不可失败初始化器覆盖超类可失败初始化器。这使您能够定义初始化不能失败的子类,即使允许父类的初始化失败。
注意,如果使用不可失败的子类初始化器覆盖可失败超类初始化器,则将委托给超类初始化器的唯一方法是强制打开可失败超类初始化器的结果。
您可以使用不可失败初始化器覆盖可失败初始化器,但反之则不行。
下面的示例定义了一个名为Document的类。这个类为一个文档建模,该文档可以用name属性初始化,该属性要么是非空字符串值,要么为空,但不能是空字符串:
class Document {
var name: String?
// this initializer creates a document with a nil name value
init() {}
// this initializer creates a document with a nonempty name value
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
下一个示例定义了一个名为AutomaticallyNamedDocument的文档子类。AutomaticallyNamedDocument子类覆盖Document引入的两个指定初始化器。这些覆盖确保AutomaticallyNamedDocument实例的初始名值为“[Untitled]”,如果实例初始化时没有名称,或者一个空字符串被传递给init(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
}
}
}
AutomaticallyNamedDocument使用不可失败的init(name:)初始化器覆盖其超类的可失败init?(name:)初始化器。因为AutomaticallyNamedDocument处理空字符串的方式与其超类不同,所以它的初始化器不需要失败,因此它提供了初始化器的一个不可失败版本。
可以在初始化器中使用强制展开来从超类调用可失败初始化器,作为子类不可失败初始化器实现的一部分。例如,下面的UntitledDocument子类总是被命名为“[Untitled]”,它在初始化过程中使用父类中的可失败init(name:)初始化器。
class UntitledDocument: Document {
override init() {
super.init(name: "[Untitled]")!
}
}
在本例中,如果调用父类的init(name:)初始化器时使用空字符串作为名称,则强制展开操作将导致运行时错误。但是,因为它是用字符串常量调用的,所以您可以看到初始化器不会失败,所以在这种情况下不会发生运行时错误。
The init! Failable Initializer
您通常定义一个可失败的初始化器,它通过在init关键字(init?)后面放置一个问号来创建适当类型的可选实例。或者,您可以定义一个可失败的初始化器,该初始化器创建一个隐式的未包装的可选实例,该实例具有适当的类型。通过在init关键字(init!)后面加上感叹号而不是问号来实现这一点。
你可以从init中委托?初始化!反之亦然,你能重写init吗?初始化!反之亦然。您还可以将init委托给init!,尽管这样做将触发一个断言,如果初始化!初始化器导致初始化失败。
Required Initializers 必须的构造器
在类初始化器定义之前编写所需的修饰符,以指示类的每个子类都必须实现该初始化器:
class SomeClass {
required init() {
// initializer implementation goes here
}
}
您还必须在必需初始化器的每个子类实现之前编写必需的修饰符,以指示初始化器需求适用于链中的其他子类。当重写所需的指定初始化器时,不需要编写覆盖修饰符:
class SomeSubclass: SomeClass {
required init() {
// subclass implementation of the required initializer goes here
}
}
注意:如果可以使用继承的初始化器满足需求,则不必提供所需初始化器的显式实现。
Setting a Default Property Value with a Closure or Function 使用闭包或者函数设置默认属性值
如果存储属性的默认值需要一些定制或设置,可以使用闭包或全局函数为该属性提供定制的默认值。无论何时初始化属性所属类型的新实例,都会调用闭包或函数,并将其返回值指定为属性的默认值。
这些类型的闭包或函数通常创建与属性类型相同的临时值,将该值调整为表示所需的初始状态,然后返回该临时值作为属性的默认值。
下面是如何使用闭包来提供默认属性值的概要:
class SomeClass {
let someProperty: SomeType = {
...
return someValue
}()
}
注意,闭包的结束大括号后面跟着一对空括号。这告诉Swift立即执行闭包。如果省略这些括号,则尝试将闭包本身分配给属性,而不是闭包的返回值。
注意:如果使用闭包初始化属性,请记住,在执行闭包时还没有初始化实例的其余部分。这意味着您不能从闭包中访问任何其他属性值,即使这些属性具有默认值。您也不能使用隐式self属性,或调用实例的任何方法。
下面的示例定义了一个称为棋盘的结构,它为国际象棋游戏建模棋盘。国际象棋是在一块8×8的棋盘上进行的,棋盘上有黑白相间的方块。
为了表示这个棋盘,棋盘结构有一个名为boardColors的属性,它是由64个Bool值组成的数组。数组中的true值表示黑色正方形,false值表示白色正方形。数组中的第一项表示黑板上左上角的正方形,数组中的最后一项表示黑板上右下角的正方形。
用一个闭包初始化boardColors数组,以设置它的颜色值:
struct Chessboard {
let boardColors: [Bool] = {
var temporaryBoard = [Bool]()
var isBlack = false
for i in 1...8 {
for j in 1...8 {
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
return temporaryBoard
}()
func squareIsBlackAt(row: Int, column: Int) -> Bool {
return boardColors[(row * 8) + column]
}
}
每当创建一个新的棋盘实例时,就会执行闭包,并计算和返回boardColors的默认值。上面示例中的闭包计算并设置名为temporaryBoard的临时数组中董事会上的每个正方形的适当颜色,并在设置完成后返回这个临时数组作为闭包的返回值。返回的数组值存储在boardColors中,可以使用squareIsBlackAt(row:column:) 实用函数查询:
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"