Swift:构造过程
一、存储属性的初始赋值
-
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
-
你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。
1、构造器
- 构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字 init 命名:
init() {
// 在此处执行构造过程
}
- 下面例子中定义了一个用来保存华氏温度的结构体
Fahrenheit
,它拥有一个Double
类型的存储型属性temperature
:
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// 打印 "The default temperature is 32.0° Fahrenheit"
2、默认属性值
-
如前所述,你可以在构造器中为存储型属性设置初始值。同样,你也可以在属性声明时为其设置默认值。
-
你可以使用更简单的方式在定义结构体
Fahrenheit
时为属性temperature
设置默认值:
struct Fahrenheit {
var temperature = 32.0
}
- 定义一个结构体时, 可以不给属性赋初始值, 也不需要自定义构造方法, 结构体会自动生成一个给所有属性赋值的构造方法
struct Vehicle {
let speed: Int
var currentSpeed: Double
}
let vehicle = Vehicle(speed: 0, currentSpeed: 0)
- 自动生成的如下方法, 这个方法不需要我们写, 系统会自动生成
init(speed: Int, currentSpeed: Double) {
self.speed = speed
self.currentSpeed = currentSpeed
}
-
但是一个类, 却不会自动生成这种方法, 当我们没有给属性默认值时, 就必须写一个构造方法, 给没有默认值的非可选属性指定一个默认值
-
下面定义的
Vehicle
, 会在编译时报错, 因为属性没有初始值, 并且类不会自动生成一个给所有属性赋初始值的构造方法
class Vehicle {
let speed: Int
var currentSpeed: Double
}
二、自定义构造过程
你可以通过输入参数和可选类型的属性来自定义构造过程,也可以在构造过程中给常量属性赋初值。
1、构造参数
-
自定义构造过程时,可以在定义中提供构造参数,指定参数值的类型和名字。构造参数的功能和语法跟函数和方法的参数相同。
-
下面例子中定义了一个包含摄氏度温度的结构体
Celsius
。它定义了两个不同的构造器:init(fromFahrenheit:)
和init(fromKelvin:)
,二者分别通过接受不同温标下的温度值来创建新的实例:
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 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0
- 第一个构造器拥有一个构造参数,其外部名字为
fromFahrenheit
,内部名字为fahrenheit
;第二个构造器也拥有一个构造参数,其外部名字为fromKelvin
,内部名字为kelvin
。这两个构造器都将唯一的参数值转换成摄氏温度值,并保存在属性temperatureInCelsius
中。
2、参数名和参数标签
- 跟函数和方法参数相同,构造参数也拥有一个在构造器内部使用的参数名和一个在调用构造器时使用的参数标签。
然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。
因此在调用构造器时,主要通过构造器中的参数名和类型来确定应该被调用的构造器。
正因为参数如此重要,如果你在定义构造器时没有提供参数标签,
Swift 会为构造器的每个参数自动生成一个参数标签。
-
以下例子中定义了一个结构体
Color
,它包含了三个常量:red
、green
和blue
。 -
Color
提供了一个构造器,其中包含三个Double
类型的构造参数。Color
也提供了第二个构造器,它只包含名为white
的Double
类型的参数,它被用于给上述三个构造参数赋予同样的值。
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)
// 报编译时错误,需要外部名称
3、不带参数标签的构造器参数
- 如果你不希望为构造器的某个参数提供参数标签,你可以使用下划线(
_
)来显式描述它的外部名,以此重写上面所说的默认行为。
struct Celsius {
var temperatureInCelsius: Double
init(_ celsius: Double){
temperatureInCelsius = celsius
}
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 为 37.0
4、可选属性类型
- 如果你定制的类型包含一个逻辑上允许取值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时间点可以赋值为空——你都需要将它定义为
可选类型
。 - 可选类型的属性将自动初始化为
nil
,表示这个属性是有意在初始化时设置为空的。
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()
// 打印 "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
- 调查问题的答案在回答前是无法确定的,因此我们将属性
response
声明为String?
类型,或者说是 可选字符串类型。当SurveyQuestion
实例化时,它将自动赋值为nil
,表明此字符串暂时还没有值。
5、构造过程中常量属性的赋值
- 你可以在构造过程中的任意时间点给常量属性指定一个值,只要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改。
注意
对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
- 你可以修改上面的
SurveyQuestion
示例,用常量属性替代变量属性text
,表示问题内容text
在SurveyQuestion
的实例被创建之后不会再被修改。
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()
// 打印 "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"
注意
如果类的常量属性, 在定义时指定了初始值, 就不能在构造方法中再次修改常量的值
只有在定义时, 没有指定常量属性的值, 才可以在构造方法中指定值
三、默认构造器
-
如果结构体或类的所有属性都有默认值,同时没有自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造器
-
这个默认构造器将简单地创建一个所有属性值都设置为默认值的实例。
-
下面例子中创建了一个类
ShoppingListItem
,它封装了购物清单中的某一物品的属性:名字、数量和购买状态:
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
1、结构体的逐一成员构造器
-
除了上面提到的默认构造器,如果结构体没有提供自定义的构造器,它们将自动获得一个逐一成员构造器,即使结构体的存储型属性没有默认值。
-
逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。
-
我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
四、值类型的构造器代理
-
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能避免多个构造器间的代码重复。
-
构造器代理的实现规则和形式在值类型和类类型中有所不同。
值类型不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给自己的其它构造器。
类则不同,它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。
-
对于值类型,你可以使用
self.init
在自定义的构造器中引用相同类型中的其它构造器。并且你只能在构造器内部调用self.init
。 -
请注意,如果你为某个
值类型
定义了一个自定义的构造器
,你将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)。 -
这种限制可以防止你为值类型增加了一个额外的且十分复杂的构造器之后,仍然有人错误的使用自动生成的构造器
注意
假如你希望默认构造器、逐一成员构造器以及你自己的自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展(extension)中,而不是写在值类型的原始定义中。
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
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)
}
}
五、类的继承和构造过程
- 类里面的所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设置初始值。 Swift 为
类
类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们分别是指定构造器
和便利构造器
。
1、指定构造器和便利构造器
-
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类合适的构造器来实现父类的初始化。
-
类倾向于拥有少量指定构造器,普遍的是一个类拥有一个指定构造器。指定构造器在初始化的地方通过“管道”将初始化过程持续到父类链。
-
每一个类都必须至少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件
-
便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。
-
你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。
2、指定构造器和便利构造器的语法
- 类的指定构造器的写法跟值类型简单构造器一样:
init(parameters) {
statements
}
- 便利构造器也采用相同样式的写法,但需要在
init
关键字之前放置convenience
关键字,并使用空格将它们俩分开:
convenience init(parameters) {
statements
}
指定构造器
和便利构造器
最大的区别:
指定构造器
只能调用父类的指定构造器
, 没有父类, 不需要调用任何构造器
遍历构造器
只能调用自己的其他构造器
, 可以是自己的指定构造器
, 也可以是自己的遍历构造器
,遍历构造器
最终必须调用自己的指定构造器
3、类的构造器代理规则
- 为了简化指定构造器和便利构造器之间的调用关系,Swift 采用以下三条规则来限制构造器之间的代理调用:
规则 1: 指定构造器必须调用其直接父类的的指定构造器。
规则 2: 便利构造器必须调用同类中定义的其它构造器。
规则 3: 便利构造器最后必须调用指定构造器。
- 一个更方便记忆的方法是:
指定构造器必须总是向上代理
便利构造器必须总是横向代理
4、两段式构造过程
- Swift 中类的构造过程包含两个阶段。
第一个阶段,类中的每个存储型属性赋一个初始值。
当每个存储型属性的初始值被赋值后,第二阶段开始,
它给每个类一次机会,在新实例准备使用之前进一步定制它们的存储型属性。
- Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程不出错地完成:
安全检查 1:
指定构造器必须保证它所在类的所有属性都必须先初始化完成,
之后才能将其它构造任务向上代理给父类中的构造器。
如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。
为了满足这一规则,指定构造器必须保证它所在类的属性在它往上代理之前先完成初始化。
安全检查 2
指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器,
如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
安全检查 3
便利构造器必须为任意属性(包括同类中定义的)赋新值之前代理调用同一类中的其它构造器,
如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
安全检查 4
构造器在第一阶段构造完成之前,不能调用任何实例方法,
不能读取任何实例属性的值,不能引用 self 作为一个值。
-
类实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,该实例才会成为有效实例,才能访问属性和调用方法。
-
以下是两段式构造过程中基于上述安全检查的构造流程展示:
阶段 1:
1、 某个指定构造器或便利构造器被调用。
2、 完成新实例内存的分配,但此时内存还没有被初始化。
3、 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
4、 指定构造器将调用父类的构造器,完成父类属性的初始化。
5、 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部。
6、 当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,
这个实例的内存被认为已经完全初始化。此时阶段 1 完成。
阶段 2:
1、从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。
构造器此时可以访问 self、修改它的属性并调用实例方法等等。
2、最终,任意构造器链中的便利构造器可以有机会定制实例和使用 self。
5、构造器的继承和重写
- 在下面的例子中定义了一个叫
Vehicle
的基类。 - 基类中声明了一个存储型属性
numberOfWheels
,它是默认值为0
的Int
类型的存储型属性。 -
numberOfWheels
属性用于创建名为descrpiption
的String
类型的计算型属性:
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
-
Vehicle
类只为存储型属性提供默认值,也没有提供自定义构造器。因此,它会自动获得一个默认构造器, 自动获得的默认构造器总是类中的指定构造器
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
- 下面例子中定义了一个
Vehicle
的子类Bicycle
:
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
- 子类 Bicycle 定义了一个自定义指定构造器
init()
。这个指定构造器和父类的指定构造器相匹配,所以Bicycle
中的指定构造器需要带上override
修饰符。 - 如果你创建一个
Bicycle
实例,你可以调用继承的description
计算型属性去查看属性numberOfWheels
是否有改变:
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// 打印 "Bicycle: 2 wheel(s)"
注意
子类可以在初始化时修改继承来的变量属性,但是不能修改继承来的常量属性。
6、构造器的自动继承
-
子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。
-
假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则适用:
规则 1:
如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。
规则 2:
如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,
还是提供了自定义实现——它将自动继承父类所有的便利构造器。
注意
对于规则 2,子类可以将父类的指定构造器实现为便利构造器。
7、指定构造器和便利构造器实践
-
接下来的例子将在实践中展示指定构造器、便利构造器以及构造器的自动继承。
-
这个例子定义了包含三个类
Food
、RecipeIngredient
以及ShoppingListItem
的类层次结构,并将演示它们的构造器是如何相互作用的。 -
类层次中的基类是
Food
,它是一个简单的用来封装食物名字的类。Food
类引入了一个叫做name
的String
类型的属性,并且提供了两个构造器来创建Food
实例:
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
- 类类型没有默认的逐一成员构造器,所以
Food
类提供了一个接受单一参数name
的指定构造器。这个构造器可以使用一个特定的名字来创建新的Food
实例:
let namedMeat = Food(name: "Bacon")
// namedMeat 的名字是 "Bacon"
-
Food
类同样提供了一个没有参数的便利构造器init()
。这个init()
构造器为新食物提供了一个默认的占位名字,通过横向代理到指定构造器init(name: String)
并给参数name
赋值为[Unnamed]
来实现:
let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]
- 类层级中的第二个类是
Food
的子类RecipeIngredient
。RecipeIngredient
类用来表示食谱中的一项原料。它引入了Int
类型的属性quantity
(以及从Food
继承过来的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
类拥有一个指定构造器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
实例:
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
- 类层级中第三个也是最后一个类是
RecipeIngredient
的子类,叫做ShoppingListItem
。这个类构建了购物单中出现的某一种食谱原料。
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}
-
由于它为自己引入的所有属性都提供了默认值,并且自己没有定义任何构造器,
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 ✘
六、可失败构造器
-
如果一个类、结构体或枚举类型的对象,在构造过程中有可能失败,则为其定义一个可失败构造器是很有用的。
-
你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在
init
关键字后面添加问号(init?
)。
注意
可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同。
- 可失败构造器会创建一个类型为自身类型的可选类型的对象。你通过
return nil
语句来表明可失败构造器在何种情况下应该 “失败”。
注意
严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用return nil
表明可失败构造器构造失败,而不要用关键字return
来表明构造成功。
- 下例中,定义了一个名为
Animal
的结构体,其中有一个名为species
的String
类型的常量属性。同时该结构体还定义了一个接受一个名为species
的String
类型参数的可失败构造器。这个可失败构造器检查传入的参数是否为一个空字符串。如果为空字符串,则构造失败。否则,species
属性被赋值,构造成功。
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty {
return nil
}
self.species = species
}
}
- 你可以通过该可失败构造器来尝试构建一个
Animal
的实例,并检查构造过程是否成功:
let someCreature = Animal(species: "Giraffe")
// someCreature 的类型是 Animal? 而不是 Animal
if let giraffe = someCreature {
print("An animal was initialized with a species of \(giraffe.species)")
}
// 打印 "An animal was initialized with a species of Giraffe"
- 如果你给该可失败构造器传入一个空字符串作为其参数,则会导致构造失败:
let anonymousCreature = Animal(species: "")
// anonymousCreature 的类型是 Animal?, 而不是 Animal
if anonymousCreature == nil {
print("The anonymous creature could not be initialized")
}
// 打印 "The anonymous creature could not be initialized"
1、枚举类型的可失败构造器
- 你可以通过一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。如果提供的参数无法匹配任何枚举成员,则构造失败。
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.")
}
// 打印 "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.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."
2、带原始值的枚举类型的可失败构造器
- 带原始值的枚举类型会自带一个可失败构造器
init?(rawValue:)
,该可失败构造器有一个名为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.")
}
// 打印 "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.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."
注意:
如果枚举没有初始值, 那么就不会自动生成init?(rawValue:)
构造器
3、构造失败的传递
-
类,结构体,枚举的可失败构造器可以横向代理到同类型中的其他可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。
-
无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。
注意
可失败构造器也可以代理到其它的非可失败构造器。通过这种方式,你可以增加一个可能的失败状态到现有的构造过程中。
- 下面这个例子,定义了一个名为
CartItem
的Product
类的子类。这个类建立了一个在线购物车中的物品的模型,它有一个名为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
可失败构造器首先验证接收的quantity
值是否大于等于1
。倘若quantity
值无效,则立即终止整个构造过程,返回失败结果,且不再执行余下代码。同样地,Product
的可失败构造器首先检查name
值,假如name
值为空字符串,则构造器立即执行失败。 -
如果你通过传入一个非空字符串
name
以及一个值大于等于1
的quantity
来创建一个CartItem
实例,那么构造方法能够成功被执行:
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印 "Item: sock, quantity: 2"
- 倘若你以一个值为
0
的quantity
来创建一个CartItem
实例,那么将导致CartItem
构造器失败:
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
print("Unable to initialize zero shirts")
}
// 打印 "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")
}
// 打印 "Unable to initialize one unnamed product"
4、重写一个可失败构造器
-
如同其它的构造器,你可以在子类中重写父类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个父类的可失败构造器。这使你可以定义一个不会构造失败的子类,即使父类的构造器允许构造失败。
-
注意,当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理到父类的可失败构造器的唯一方式是对父类的可失败构造器的返回值进行强制解包。
注意
你可以用非可失败构造器重写可失败构造器,但反过来却不行。
- 下例定义了一个名为
Document
的类,name
属性的值必须为一个非空字符串或nil
,但不能是一个空字符串:
class Document {
var name: String?
// 该构造器创建了一个 name 属性的值为 nil 的 document 实例
init() {}
// 该构造器创建了一个 name 属性的值为非空字符串的 document 实例
init?(name: String) {
self.name = name
if name.isEmpty { return nil }
}
}
- 下面这个例子,定义了一个
Document
类的子类AutomaticallyNamedDocument
。这个子类重写了父类的两个指定构造器,确保了无论是使用init()
构造器,还是使用init(name:)
构造器并为参数传递空字符串,生成的实例中的name
属性总有初始"[Untitled]"
:
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:)
。因为子类用另一种方式处理了空字符串的情况,所以不再需要一个可失败构造器,因此子类用一个非可失败构造器代替了父类的可失败构造器。 -
你可以在子类的非可失败构造器中使用强制解包来调用父类的可失败构造器。比如,下面的
UntitledDocument
子类的name
属性的值总是"[Untitled]"
,它在构造过程中使用了父类的可失败构造器init?(name:)
:
class UntitledDocument: Document {
override init() {
super.init(name: "[Untitled]")!
}
}
- 在这个例子中,如果在调用父类的可失败构造器
init?(name:)
时传入的是空字符串,那么强制解包操作会引发运行时错误。不过,因为这里是通过非空的字符串常量来调用它,所以并不会发生运行时错误。
5、init!可失败构造器
-
通常来说我们通过在
init
关键字后添加问号的方式(init?
)来定义一个可失败构造器,但你也可以通过在 init 后面添加惊叹号的方式来定义一个可失败构造器(init!
),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。 -
你可以在
init?
中代理到init!
,反之亦然。你也可以用init?
重写init!
,反之亦然。你还可以用init
代理到init!
,不过,一旦init!
构造失败,则会触发一个断言。
七、必要构造器
- 在类的构造器前添加
required
修饰符表明所有该类的子类都必须实现该构造器:
class SomeClass {
required init() {
// 构造器的实现代码
}
}
- 在子类重写父类的必要构造器时,必须在子类的构造器前也添加
required
修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加override
修饰符:
class SomeSubclass: SomeClass {
required init() {
// 构造器的实现代码
}
}
注意
如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现。
八、通过闭包或函数设置属性的默认值
-
如果某个存储型属性的默认值需要一些定制或设置,你可以使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
-
这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。
-
下面模板介绍了如何用闭包为属性提供默认值:
class SomeClass {
let someProperty: SomeType = {
// 在这个闭包中给 someProperty 创建一个默认值
// someValue 必须和 SomeType 类型相同
return someValue
}()
}
- 注意闭包结尾的花括号后面接了一对空的小括号。这用来告诉 Swift 立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
注意
如果你使用闭包来初始化属性,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能在闭包里访问其它属性,即使这些属性有默认值。同样,你也不能使用隐式的self
属性,或者调用任何实例方法。