构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例使用前有个过程是必须的,它包括设置实例中每个存储属性的初始值和执行其他必须的设置或构造过程。
你要通过定义构造器来实现构造过程,它就像用来创建特定类型新实例的特殊方法。与 Objective-C 中的构造器不同,Swift 的构造器没有返回值。它们的主要任务是保证某种类型的新实例在第一次使用前完成正确的初始化。
类的实例也可以通过实现析构器来执行它释放之前自定义的清理工作。想了解更多关于析构器的内容,请参考析构过程。
存储属性的初始赋值
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
你可以在构造器中为存储型属性设置初始值,也可以在定义属性时分配默认值。以下小节将详细介绍这两种方法。
注意
当你为存储型属性分配默认值或者在构造器中为设置初始值时,它们的值是被直接设置的,不会触发任何属性观察者。
构造器
构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何形参的实例方法,以关键字 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”
这个结构体定义了一个不带形参的构造器 init,并在里面将存储型属性 temperature 的值初始化为 32.0(华氏温度下水的冰点)。
默认属性值
如前所述,你可以在构造器中为存储型属性设置初始值。同样,你也可以在属性声明时为其设置默认值。
注意
如果一个属性总是使用相同的初始值,那么为其设置一个默认值比每次都在构造器中赋值要好。两种方法的最终结果是一样的,只不过使用默认值让属性的初始化和声明结合得更紧密。它能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承等特性,后续章节将讲到。
你可以通过在属性声明时为 temperature 提供默认值来使用更简单的方式定义结构体 Fahrenheit :
struct Fahrenheit {
var temperature = 32.0
}
自定义构造过程
你可以通过输入形参和可选属性类型来自定义构造过程,也可以在构造过程中分配常量属性。这些都将在后面章节中提到。
形参的构造过程
自定义构造过程时,可以在定义中提供构造形参,指定其值的类型和名字。构造形参的功能和语法跟函数和方法的形参相同。
下面例子中定义了一个用来保存摄氏温度的结构体 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
中。
形参命名和实参标签
跟函数和方法形参相同,构造形参可以同时使用在构造器里使用的形参命名和一个外部调用构造器时使用的实参标签。
然而,构造器并不像函数和方法那样在括号前有一个可辨别的方法名。因此在调用构造器时,主要通过构造器中形参命名和类型来确定应该被调用的构造器。正因如此,如果你在定义构造器时没有提供实参标签,Swift 会为构造器的每个形参自动生成一个实参标签。
以下例子中定义了一个结构体 Color
,它包含了三个常量:red
、green
和 blue
。这些属性可以存储 0.0
到 1.0
之间的值,用来表明颜色中红、绿、蓝成分的含量。
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)
// 报编译期错误-需要实参标签
不带实参标签的构造器形参
如果你不希望构造器的某个形参使用实参标签,可以使用下划线(_
)来代替显式的实参标签来重写默认行为。
下面是之前 形参的构造过程 中 Celsius
例子的扩展,多了一个用已经的摄氏表示的 Double
类型值来创建新的 Celsius
实例的额外构造器:
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 为 37.0
构造器调用 Celsius(37.0)
意图明确,不需要实参标签。因此适合使用 init(_ celsius: Double)
这样的构造器,从而可以通过提供未命名的 Double
值来调用构造器。
可选属性类型
如果你自定义的类型有一个逻辑上允许值为空的存储型属性——无论是因为它无法在初始化时赋值,还是因为它在之后某个时机可以赋值为空——都需要将它声明为 可选类型
。可选类型的属性将自动初始化为 nil
,表示这个属性是特意在构造过程设置为空。
下面例子中定义了类 SurveyQuestion
,它包含一个可选 String
属性 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()
// 打印“Do you like cheese?”
cheeseQuestion.response = "Yes, I do like cheese."
调查问题的答案在询问前是无法确定的,因此我们将属性 response
声明为 String?
类型,或者说是 “可选类型 String
“。当 SurveyQuestion
的实例初始化时,它将自动赋值为 nil
,表明“暂时还没有字符“。
构造过程中常量属性的赋值
你可以在构造过程中的任意时间点给常量属性赋值,只要在构造过程结束时它设置成确定的值。一旦常量属性被赋值,它将永远不可更改。
注意
对于类的实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
你可以修改上面的 SurveyQuestion
示例,用常量属性替代变量属性 text
,表示问题内容 text
在 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()
// 打印“How about beets?”
beetsQuestion.response = "I also like beets. (But not with cheese.)"
注:
1.结构体创建过程中:给属性赋值的时候要按照顺序,且结构体属性可赋初值。
2.自定义构造函数init:通过字符串解析出相应值,不需要返回值。
3.写了任何一个自定义的构造函数,默认的构造函数失效,要想使用默认的构造函数需要再写一个和默认的构造函数一模一样的构造函数。
4.传入参数名和结构体内定义的属性名一样,用self防止歧义。
5.如果一个类、结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有用的。为了妥善处理这种构造过程中可能会失败的情况。可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在 init 关键字后面加添问号 (init?)。
guard 和 if 大多数情况下一样,但在可选型解包中能创建解包后的变量,这个变量不再是可选型,而能被使用。
6.结构体和类中的函数称作方法。
在结构体中声明方法,参数名默认是省略的,要想显示出来必须声明外部参数名,但是对于构造函数而言,参数名默认会显示出来。