注意当你为存储型属性分配默认值或者在构造器中设置初始值时,它们的值是被直接设置的,不会触发任何属性观察者。
init
命名:Fahrenheit
,它拥有一个 Double
类型的存储型属性 temperature
:init
,并在里面将存储型属性 temperature
的值初始化为 32.0
(华氏温度下水的冰点)。注意如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的最终结果是一样的,只不过使用默认值让属性的初始化和声明结合得更紧密。它能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性,后续章节将讲到。
temperature
提供默认值来使用更简单的方式定义结构体 Fahrenheit
:Celsius
。它定义了两个不同的构造器:init(fromFahrenheit:)
和 init(fromKelvin:)
,二者分别通过接受不同温标下的温度值来创建新的实例:fromFahrenheit
,形参命名为 fahrenheit
;第二个构造器也拥有一个构造形参,其实参标签为 fromKelvin
,形参命名为 kelvin
。这两个构造器都将单一的实参转换成摄氏温度值,并保存在属性 temperatureInCelsius
中。Color
,它包含了三个常量:red
、green
和 blue
。这些属性可以存储 0.0
到 1.0
之间的值,用来表明颜色中红、绿、蓝成分的含量。Color
提供了一个构造器,为红蓝绿提供三个合适 Double
类型的形参命名。Color
也提供了第二个构造器,它只包含名为 white
的 Double
类型的形参,它为三个颜色的属性提供相同的值。Color
实例:_
)来代替显式的实参标签来重写默认行为。Celsius(37.0)
意图明确,不需要实参标签。因此适合使用 init(_ celsius: Double)
这样的构造器,从而可以通过提供未命名的 Double
值来调用构造器。可选类型
。可选类型的属性将自动初始化为 nil
,表示这个属性是特意在构造过程设置为空。SurveyQuestion
,它包含一个可选 String
属性 response
:response
声明为 String?
类型,或者说是 “可选类型 String
“。当 SurveyQuestion
的实例初始化时,它将自动赋值为 nil
,表明“暂时还没有字符“。注意对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
SurveyQuestion
示例,用常量属性替代变量属性 text
,表示问题内容 text
在 SurveyQuestion
的实例被创建之后不会再被修改。尽管 text
属性现在是常量,我们仍然可以在类的构造器中设置它的值:ShoppingListItem
,它封装了购物清单中的某一物品的名字(name
)、数量(quantity
)和购买状态 purchase state
:ShoppingListItem
类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个将为所有属性设置默认值的并创建实例的默认构造器(由于 name
属性是可选 String
类型,它将接收一个默认 nil
的默认值,尽管代码中没有写出这个值)。上面例子中使用默认构造器创造了一个 ShoppingListItem
类的实例(使用 ShoppingListItem()
形式的构造器语法),并将其赋值给变量 item
。Size
,它包含两个属性 width
和 height
。根据这两个属性默认赋值为 0.0
,它们的类型被推断出来为 Double
。Size
自动获得了一个逐一成员构造器 init(width:height:)
。你可以用它来创建新的 Size 实例:Size
结构体的 height
和 width
属性各有一个默认值。你可以省略两者或两者之一,对于被省略的属性,构造器会使用默认值。举个例子:self.init
在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用 self.init
。注意
Rect
,用来代表几何矩形。这个例子需要两个辅助的结构体 Size
和 Point
,它们各自为其所有的属性提供了默认初始值 0.0
。Rect
创建实例——使用含有默认值的 origin
和 size
属性来初始化;提供指定的 origin
和 size
实例来初始化;提供指定的 center
和 size
来初始化。在下面 Rect
结构体定义中,我们为这三种方式提供了三个自定义的构造器:Rect
构造器 init()
,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器是函数体是空的,使用一对大括号 {}
来表示。调用这个构造器将返回一个 Rect
实例,它的 origin
和 size
属性都使用定义时的默认值 Point(x: 0.0, y: 0.0)
和 Size(width: 0.0, height: 0.0)
:Rect
构造器 init(origin:size:)
,在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一样的。这个构造器只是简单地将 origin
和 size
的实参值赋给对应的存储型属性:Rect
构造器 init(center:size:)
稍微复杂一点。它先通过 center
和 size
的值计算出 origin
的坐标,然后再调用(或者说代理给)init(origin:size:)
构造器来将新的 origin
和 size
值赋值到对应的属性中:init(center:size:)
可以直接将 origin
和 size
的新值赋值到对应的属性中。然而,构造器 init(center:size:)
通过使用提供了相关功能的现有构造器将会更加便捷(而且意图更清晰)。注意
init
关键字之前放置 convenience
关键字,并使用空格将它们俩分开:注意这些规则不会影响类的实例如何创建。任何上图中展示的构造器都可以用来创建完全初始化的实例。这些规则只影响类的构造器如何实现。
注意Swift 的两段式构造过程跟 Objective-C 中的构造过程类似。最主要的区别在于阶段 1,Objective-C 给每一个属性赋值0
或空值(比如说0
或nil
)。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以0
或nil
作为合法默认值的情况。
self
作为一个值。self
、修改它的属性并调用实例方法等等。self
。注意
override
修饰符。即使你重写的是系统自动提供的默认构造器,也需要带上 override
修饰符,具体内容请参考 默认构造器。override
修饰符会让编译器去检查父类中是否有相匹配的指定构造器,并验证构造器参数是否被按预想中被指定。注意当你重写一个父类的指定构造器时,你总是需要写override
修饰符,即使是为了实现子类的便利构造器。
override
修饰符。Vehicle
的基类。基类中声明了一个存储型属性 numberOfWheels
,它是默认值为 Int
类型的 0
。numberOfWheels
属性用在一个描述车辆特征 String
类型为 descrpiption
的计算型属性中:Vehicle
类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器,具体内容请参考 默认构造器。默认构造器(如果有的话)总是类中的指定构造器,可以用于创建 numberOfWheels
为 0
的 Vehicle
实例:Vehicle
的子类 Bicycle
:Bicycle
定义了一个自定义指定构造器 init()
。这个指定构造器和父类的指定构造器相匹配,所以 Bicycle
中这个版本的构造器需要带上 override
修饰符。Bicycle
的构造器 init()
以调用 super.init()
方法开始,这个方法的作用是调用 Bicycle
的父类 Vehicle
的默认构造器。这样可以确保 Bicycle
在修改属性之前,它所继承的属性 numberOfWheels
能被 Vehicle
类初始化。在调用 super.init()
之后,属性 numberOfWheels
的原值被新值 2
替换。Bicycle
实例,你可以调用继承的 description
计算型属性去查看属性 numberOfWheels
是否有改变:super.init()
的调用。Vehicle
的子类 Hoverboard
,只设置它的 color
属性。这个构造器依赖隐式调用父类的构造器来完成,而不是显示调用 super.init()
。Hoverboard
的实例用 Vehicle
构造器里默认的轮子数量。注意子类可以在构造过程修改继承来的变量属性,但是不能修改继承来的常量属性。
注意子类可以将父类的指定构造器实现为便利构造器来满足规则 2。
Food
、RecipeIngredient
以及 ShoppingListItem
的层级结构,并将演示它们的构造器是如何相互作用的。Food
,它是一个简单的用来封装食物名字的类。Food
类引入了一个叫做 name
的 String
类型的属性,并且提供了两个构造器来创建 Food
实例:Food
的构造器链:Food
类提供了一个接受单一参数 name
的指定构造器。这个构造器可以使用一个特定的名字来创建新的 Food
实例:Food
类中的构造器 init(name: String)
被定义为一个指定构造器,因为它能确保 Food
实例的所有存储型属性都被初始化。Food
类没有父类,所以 init(name: String)
构造器不需要调用 super.init()
来完成构造过程。Food
类同样提供了一个没有参数的便利构造器 init()
。这个 init()
构造器为新食物提供了一个默认的占位名字,通过横向代理到指定构造器 init(name: String)
并给参数 name
赋值为 [Unnamed]
来实现:Food
的子类 RecipeIngredient
。RecipeIngredient
类用来表示食谱中的一项原料。它引入了 Int
类型的属性 quantity
(以及从 Food
继承过来的 name
属性),并且定义了两个构造器来创建 RecipeIngredient
实例:RecipeIngredient
类的构造器链:RecipeIngredient
类拥有一个指定构造器 init(name: String, quantity: Int)
,它可以用来填充 RecipeIngredient
实例的所有属性值。这个构造器一开始先将传入的 quantity
实参赋值给 quantity
属性,这个属性也是唯一在 RecipeIngredient
中新引入的属性。随后,构造器向上代理到父类 Food
的 init(name: String)
。这个过程满足 两段式构造过程 中的安全检查 1。RecipeIngredient
也定义了一个便利构造器 init(name: String)
,它只通过 name
来创建 RecipeIngredient
的实例。这个便利构造器假设任意 RecipeIngredient
实例的 quantity
为 1
,所以不需要显式的质量即可创建出实例。这个便利构造器的定义可以更加方便和快捷地创建实例,并且避免了创建多个 quantity
为 1
的 RecipeIngredient
实例时的代码重复。这个便利构造器只是简单地横向代理到类中的指定构造器,并为 quantity
参数传递 1
。RecipeIngredient
的便利构造器 init(name: String)
使用了跟 Food
中指定构造器 init(name: String)
相同的形参。由于这个便利构造器重写了父类的指定构造器 init(name: String)
,因此必须在前面使用 override
修饰符(参见 构造器的继承和重写)。RecipeIngredient
将父类的指定构造器重写为了便利构造器,但是它依然提供了父类的所有指定构造器的实现。因此,RecipeIngredient
会自动继承父类的所有便利构造器。RecipeIngredient
的父类是 Food
,它有一个便利构造器 init()
。这个便利构造器会被 RecipeIngredient
继承。这个继承版本的 init()
在功能上跟 Food
提供的版本是一样的,只是它会代理到 RecipeIngredient
版本的 init(name: String)
而不是 Food
提供的版本。RecipeIngredient
实例:RecipeIngredient
的子类,叫做 ShoppingListItem
。这个类构建了购物单中出现的某一种食谱原料。ShoppingListItem
引入了一个 Boolean(布尔类型) 的属性 purchased
,它的默认值是 false
。ShoppingListItem
还添加了一个计算型属性 description
,它提供了关于 ShoppingListItem
实例的一些文字描述:注意ShoppingListItem
没有定义构造器来为purchased
提供初始值,因为添加到购物单的物品的初始状态总是未购买。
ShoppingListItem
将自动继承所有父类中的指定构造器和便利构造器。ShoppingListItem
的新实例:breakfastList
,它包含了三个 ShoppingListItem
实例,因此数组的类型也能被自动推导为 [ShoppingListItem]
。在数组创建完之后,数组中第一个 ShoppingListItem
实例的名字从 [Unnamed]
更改为 Orange juice
,并标记状态为已购买。打印数组中每个元素的描述显示了它们都已按照预期被赋值。init
关键字后面添加问号(init?
)。注意可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。
return nil
语句来表明可失败构造器在何种情况下应该 “失败”。注意严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用return nil
表明可失败构造器构造失败,而不要用关键字return
来表明构造成功。
init(exactly:)
构造器。如果类型转换不能保持值不变,则这个构造器构造失败。Animal
的结构体,其中有一个名为 species
的 String
类型的常量属性。同时该结构体还定义了一个接受一个名为 species
的 String
类型形参的可失败构造器。这个可失败构造器检查传入的species
值是否为一个空字符串。如果为空字符串,则构造失败。否则,species
属性被赋值,构造成功。Animal
的实例,并检查构造过程是否成功:species
,则会导致构造失败:注意检查空字符串的值(如""
,而不是"Giraffe"
)和检查值为nil
的可选类型的字符串是两个完全不同的概念。上例中的空字符串(""
)其实是一个有效的,非可选类型的字符串。这里我们之所以让Animal
的可失败构造器构造失败,只是因为对于Animal
这个类的species
属性来说,它更适合有一个具体的值,而不是空字符串。
TemperatureUnit
的枚举类型。其中包含了三个可能的枚举状态(Kelvin
、Celsius
和 Fahrenheit
),以及一个根据表示温度单位的 Character
值找出合适的枚举成员的可失败构造器:init?(rawValue:)
,该可失败构造器有一个合适的原始值类型的 rawValue
形参,选择找到的相匹配的枚举成员,找不到则构造失败。TemperatureUnit
的例子可以用原始值类型的 Character
和进阶的 init?(rawValue:)
构造器重写为:注意可失败构造器也可以代理到其它的不可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。
CartItem
的 Product
类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为 quantity
的常量存储型属性,并确保该属性的值至少为 1
:CartItem
可失败构造器首先验证接收的 quantity
值是否大于等于 1 。倘若 quantity
值无效,则立即终止整个构造过程,返回失败结果,且不再执行余下代码。同样地,Product
的可失败构造器首先检查 name
值,假如 name
值为空字符串,则构造器立即执行失败。name
以及一个值大于等于 1 的 quantity
来创建一个 CartItem
实例,那么构造方法能够成功被执行:quantity
来创建一个 CartItem
实例,那么将导致 CartItem
构造器失败:name
来创建一个 CartItem
实例,那么将导致父类 Product
的构造过程失败:注意你可以用非可失败构造器重写可失败构造器,但反过来却不行。
Document
的类。这个类模拟一个文档并可以用 name
属性来构造,属性的值必须为一个非空字符串或 nil
,但不能是一个空字符串:Document
类的子类 AutomaticallyNamedDocument
。这个子类重写了所有父类引入的指定构造器。这些重写确保了无论是使用 init()
构造器,还是使用 init(name:)
构造器,在没有名字或者形参传入空字符串时,生成的实例中的 name
属性总有初始值 "[Untitled]"
:AutomaticallyNamedDocument
用一个不可失败构造器 init(name:)
重写了父类的可失败构造器 init?(name:)
。因为子类用另一种方式处理了空字符串的情况,所以不再需要一个可失败构造器,因此子类用一个不可失败构造器代替了父类的可失败构造器。UntitledDocument
子类的 name
属性的值总是 "[Untitled]"
,它在构造过程中使用了父类的可失败构造器 init?(name:)
:init?(name:)
时传入的是空字符串,那么强制解包操作会引发运行时错误。不过,因为这里是通过字符串常量来调用它,构造器不会失败,所以并不会发生运行时错误。init
关键字后添加问号的方式(init?
)来定义一个可失败构造器,但你也可以通过在 init
后面添加感叹号的方式来定义一个可失败构造器(init!
),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。init?
中代理到 init!
,反之亦然。你也可以用 init?
重写 init!
,反之亦然。你还可以用 init
代理到 init!
,不过,一旦 init!
构造失败,则会触发一个断言。required
修饰符表明所有该类的子类都必须实现该构造器:required
修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加 override
修饰符:注意如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。
注意如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的self
属性,或者调用任何实例方法。
Chessboard
,它构建了西洋跳棋游戏的棋盘,西洋跳棋游戏在一副黑白格交替的 8 x 8 的棋盘中进行的:Chessboard
结构体定义了一个属性 boardColors
,它是一个包含 64
个 Bool
值的数组。在数组中,值为 true
的元素表示一个黑格,值为 false
的元素表示一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表棋盘上右下角的格子。boardColors
数组是通过一个闭包来初始化并设置颜色值的: