构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例使用前有个过程是必须的,它包括设置实例中每个存储属性的初始值和执行其他必须的设置或构造过程。
构造过程通过构造器来实现,swift的构造器没有返回值,构造器的主要任务是保证某种类型的新实例在第一次使用前完成正确的初始化。
1、存储属性的初始赋值
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
- 在
构造器
中为存储属性赋初始值,以关键字init
命名:
init() {
// 在此处执行构造过程
}
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”
- 在声明时设置
默认属性值
struct Fahrenheit {
var temperature = 32.0
}
注意:当你为存储型属性分配默认值或者在构造器中为设置初始值时,它们的值是被直接设置的,不会触发任何属性观察者。
2、自定义构造过程
(1)形参的构造过程
自定义构造过程时,可以在定义中提供构造形参,指定其值的类型和名字。构造形参的功能和语法跟函数和方法的形参相同。
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
(2)形参命名和实参标签
跟函数和方法形参相同,构造形参可以同时使用在构造器里使用的形参命名和一个外部调用构造器时使用的实参标签。
struct Color {
let red, green, blue: Double
//构造器1
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
//构造器2
init(white: Double) {
red = white
green = white
blue = white
}
}
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
注意:构造器并不像函数和方法那样在括号前有一个可辨别的方法名。因此在调用构造器时,主要通过构造器中形参命名和类型来确定应该被调用的构造器,如果在定义构造器时没有提供实参标签,Swift 会为构造器的每个形参自动生成一个实参标签。
(3)不带实参标签的构造器形参
如果不希望构造器的某个形参使用实参标签,可以使用下划线(_
)来代替显式的实参标签来重写默认行为。
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
(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."
(5)构造过程中常量属性的赋值
可以在构造过程中的任意时间点给常量属性赋值,只要在构造过程结束时它设置成确定的值。一旦常量属性被赋值,它将永远不可更改。
class SurveyQuestion {
let text: String
var response: String?
//尽管 text 属性现在是常量,仍然可以在类的构造器中设置它的值:
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.)"
注意:对于类的实例来说,常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
3、默认构造器
如果结构体或类为所有属性提供了默认值,又没有提供任何自定义的构造器,那么 Swift 会给这些结构体或类提供一个默认构造器
。这个默认构造器将简单地创建一个所有属性值都设置为它们默认值的实例。
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
//使用默认构造器创造了一个 ShoppingListItem 类的实例
var item = ShoppingListItem()
(1)结构体的逐一成员构造器
结构体如果没有定义任何自定义构造器,它们将自动获得一个逐一成员构造器。不像默认构造器,即使存储型属性没有默认值,结构体也能会获得逐一成员构造器。
逐一成员构造器是用来初始化结构体新实例里成员属性的快捷方法。新实例的属性初始值可以通过名字传入逐一成员构造器中。
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
当你调用一个逐一成员构造器时,可以省略任何一个有默认值的属性,对于被省略的属性,构造器会使用默认值:
let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// 打印 "0.0 2.0"
let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// 打印 "0.0 0.0"
4、值类型的构造器代理
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理
,它能避免多个构造器间的代码重复。
构造器代理的实现规则和形式在值类型和类类型中有所不同,在值类型中有以下特点:
- 值类型(结构体和枚举类型)不支持继承,只能代理给自己的其它构造器。
- 对于值类型,可以使用
self.init
在自定义的构造器中引用相同类型中的其它构造器。并且只能在构造器内部调用self.init
。 - 如果为某个值类型定义了一个自定义的构造器,将无法访问到默认构造器(如果是结构体,还将无法访问逐一成员构造器)
注意:如果希望默认构造器、逐一成员构造器以及自定义构造器都能用来创建实例,可以将自定义的构造器写到扩展中,而不是写在值类型的原始定义中。
下面例子定义一个自定义结构体 Rect,用来代表几何矩形:
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)
}
}
例子中提供了三个自定义的构造器:
- 构造器
init()
:在功能上跟没有自定义构造器时自动获得的默认构造器是一样的
let basicRect = Rect()
// basicRect 的 origin 是 (0.0, 0.0),size 是 (0.0, 0.0)
- 构造器
init(origin:size:)
:在功能上跟结构体在没有自定义构造器时获得的逐一成员构造器是一样的
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
// originRect 的 origin 是 (2.0, 2.0),size 是 (5.0, 5.0)
- 构造器
init(center:size:)
先通过center
和size
的值计算出origin
的坐标,然后再调用init(origin:size:)
构造器来将新的origin
和size
值赋值到对应的属性
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
// centerRect 的 origin 是 (2.5, 2.5),size 是 (3.0, 3.0)
5、类的继承和构造过程
类里面所有存储型属性——包括所有继承自父类的属性——都必须在构造过程中设初始值。
Swift 为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们被称为指定构造器
和便利构造器
。
(1)指定构造器和便利构造器
指定构造器
:
1、类中最主要的构造器。
2、指定构造器将初始化类中提供的所有属性,并调用合适的父类构造器让构造过程沿着父类链继续往上进行。
3、 类倾向于拥有极少的指定构造器,普遍的是一个类只拥有一个指定构造器
4、每一个类都必须至少拥有一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。便利构造器
:
1、便利构造器是类中比较次要的、辅助型的构造器。可以定义便利构造器来调用同一个类中的指定构造器,并为部分形参提供默认值。也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。
2、应当只在必要的时候为类提供便利构造器。
(2)指定构造器和便利构造器的语法
- 类的
指定构造器
的语法与值类型简单构造器语法:
init(parameters) {
statements
}
-
便利构造器
也采用相同样式的写法,但需要在init
关键字之前放置convenience
关键字,并使用空格将它们俩分开:
convenience init(parameters) {
statements
}
(3)类类型的构造器代理
为了简化指定构造器和便利构造器之间的调用关系,Swift 构造器之间的代理调用遵循以下三条规则:
- 指定构造器必须调用其直接父类的的指定构造器
- 便利构造器必须调用同类中定义的其它构造器
- 便利构造器最后必须调用指定构造器
也可以这么记忆:
- 指定构造器必须总是
向上代理
- 便利构造器必须总是
横向代理
如图所示,父类中包含一个指定构造器和两个便利构造器。其中一个便利构造器调用了另外一个便利构造器,而后者又调用了唯一的指定构造器。这满足了上面提到的规则 2 和 3。这个父类没有自己的父类,所以规则 1 没有用到。
子类中包含两个指定构造器和一个便利构造器。便利构造器必须调用两个指定构造器中的任意一个,因为它只能调用同一个类里的其他构造器。这满足了上面提到的规则 2 和 3。而两个指定构造器必须调用父类中唯一的指定构造器,这满足了规则 1。
下面图例中展示了一种涉及四个类的更复杂的类层级结构。它演示了指定构造器是如何在类层级中充当“漏斗”的作用,在类的构造器链上简化了类之间的相互关系。注意:这些规则不会影响类的实例如何创建。任何上图中展示的构造器都可以用来创建完全初始化的实例。这些规则只影响类的构造器如何实现。
(4)两段式构造过程
Swift 中类的构造过程包含两个阶段:
- 第一阶段:类中的每个存储型属性赋一个初始值
- 第二阶段:给每个类一次机会,在新实例准备使用之前进一步自定义它们的存储型属性
两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问,也可以防止属性被另外一个构造器意外地赋予不同的值。
注意:Swift 的两段式构造过程跟 Objective-C 中的构造过程类似。最主要的区别在于阶段 1,Objective-C 给每一个属性赋值
0
或空值(比如说0
或nil
)。Swift 的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以0
或nil
作为合法默认值的情况。
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程不出错地完成:
安全检查1:
指定构造器必须保证它所在类的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器
。一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类的属性在它往上代理之前先完成初始化。安全检查2:
指定构造器必须在为继承的属性设置新值之前向上代理调用父类构造器
。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。安全检查3:
便利构造器必须为任意属性(包括所有同类中定义的)赋新值之前代理调用其它构造器
。如果没这么做,便利构造器赋予的新值将被该类的指定构造器所覆盖。安全检查4:
构造器在第一阶段构造完成之前,不能调用任何实例方法,不能读取任何实例属性的值,不能引用 self 作为一个值
。类的实例在第一阶段结束以前并不是完全有效的。只有第一阶段完成后,类的实例才是有效的,才能访问属性和调用方法。
以下是基于上述安全检查的两段式构造过程展示:
阶段1:
1、类的某个指定构造器或便利构造器被调用
2、完成类的新实例内存的分配,但此时内存还没有被初始化
3、指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化。
4、指定构造器切换到父类的构造器,对其存储属性完成相同的任务。
5、这个过程沿着类的继承链一直往上执行,直到到达继承链的最顶部。
6、当到达了继承链最顶部,而且继承链的最后一个类已确保所有的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段 1 完成。阶段2:
1、从继承链顶部往下,继承链中每个类的指定构造器都有机会进一步自定义实例。构造器此时可以访问self
、修改它的属性并调用实例方法等等
2、最终,继承链中任意的便利构造器有机会自定义实例和使用self
在这个例子中,构造过程从对子类中一个便利构造器的调用开始。这个便利构造器此时还不能修改任何属性,它会代理到该类中的指定构造器。如安全检查 1 所示,指定构造器将确保所有子类的属性都有值。然后它将调用父类的指定构造器,并沿着继承链一直往上完成父类的构造过程。父类中的指定构造器确保所有父类的属性都有值。一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,阶段 1 完成。
以下展示了相同构造过程的阶段 2:一旦父类中的指定构造器完成调用,子类中的指定构造器可以执行更多的自定义操作(这也不是必须的)。最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执行更多的自定义操作。
(5)构造器的继承和重写
跟 Objective-C 中的子类不同,Swift 中的子类默认情况下不会继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更精细的子类继承,而在用来创建子类时的新实例时没有完全或错误被初始化。
假如希望自定义的子类中能提供一个或多个跟父类相同的构造器,可以在子类中提供这些构造器的自定义实现。
当在编写一个和父类中指定构造器相匹配的子类构造器时,实际上是在重写父类的这个指定构造器。因此,必须在定义子类构造器时带上 override
修饰符。即使重写的是系统自动提供的默认构造器,也需要带上 override
修饰符
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
如果子类的构造器没有在阶段 2
过程中做自定义操作,并且父类有一个无参数的指定构造器,你可以在所有子类的存储属性赋值之后省略 super.init()
的调用。
class Hoverboard: Vehicle {
var color: String
init(color: String) {
self.color = color
// super.init() 在这里被隐式调用
}
override var description: String {
return "\(super.description) in a beautiful \(color)"
}
}
let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver
如果编写了一个和父类便利构造器相匹配的子类构造器,由于子类不能直接调用父类的便利构造器,因此,严格意义上来讲,子类并未对一个父类构造器提供重写。最后的结果就是,在子类中“重写”一个父类便利构造器时,不需要加 override
修饰符。
(6)构造器的自动继承
子类在默认情况下不会继承父类的构造器。但是如果满足特定条件,父类构造器是可以被自动继承的。
假设你为子类中引入的所有新属性都提供了默认值,以下 2 个规则将适用:
- 如果子类没有定义任何指定构造器,它将自动继承父类所有的指定构造器。
- 如果子类提供了所有父类指定构造器的实现——无论是通过规则 1 继承过来的,还是提供了自定义实现——它将自动继承父类所有的便利构造器。
注意:子类可以将父类的指定构造器实现为便利构造器来满足规则 2。
(7)指定构造器和便利构造器实践
定义一个包含三个类 Food
、RecipeIngredient
以及 ShoppingListItem
的层级结构,并将演示它们的构造器是如何相互作用的。
- 基类
Food
引入了一个叫做name
的String
类型的属性,并且提供了两个构造器来创建Food
实例:
class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
Food
的构造器链如下图所示:
Food
类中的构造器
init(name: String)
被定义为一个指定构造器,因为它能确保
Food
实例的所有存储型属性都被初始化。
Food
类没有父类,所以
init(name: String)
构造器不需要调用
super.init()
来完成构造过程。
let namedMeat = Food(name: "Bacon")
// namedMeat 的名字是 "Bacon"
Food
类同样提供了一个没有参数的便利构造器 init()
。这个 init()
构造器为新食物提供了一个默认的占位名字,通过横向代理到指定构造器 init(name: String)
并给参数 name
赋值为 [Unnamed]
来实现:
let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]
-
RecipeIngredient
继承于Food
,它引入了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
类的构造器链如下图所示:
RecipeIngredient
类拥有一个指定构造器
init(name: String, quantity: Int)
,它先将传入的
quantity
实参赋值给
quantity
属性,随后向上代理到父类
Food
的
init(name: String)
RecipeIngredient
也定义了一个便利构造器 init(name: String)
,它只通过 name
来创建 RecipeIngredient
的实例。这个便利构造器横向代理到类中的指定构造器,并为 quantity
参数传递 1
。
RecipeIngredient
的便利构造器 init(name: String)
使用了跟 Food
中指定构造器 init(name: String)
相同的形参。由于这个便利构造器重写了父类的指定构造器 init(name: String)
,因此必须在前面使用 override
修饰符
尽管 RecipeIngredient
将父类的指定构造器重写为了便利构造器,但是它依然提供了父类的所有指定构造器的实现。因此,RecipeIngredient
会自动继承父类的所有便利构造器。
RecipeIngredient
实例可以用以下三种方法来创建:
//name:[Unnamed],quantity:1
let oneMysteryItem = RecipeIngredient()
//name:Bacon,quantity:1
let oneBacon = RecipeIngredient(name: "Bacon")
//name:Eggs,quantity:6
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
-
ShoppingListItem
继承RecipeIngredient
,引入了一个Boolean
的属性purchased
,默认值是false
。ShoppingListItem
还添加了一个计算型属性description
,它提供了关于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 ✘
6、可失败构造器
可失败构造器
:“失败”是指如给构造器传入无效的形参,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。
为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在 init
关键字后面添加问号(init?
)
可失败构造器会创建一个类型为自身类型的可选类型的对象。你通过 return nil
语句来表明可失败构造器在何种情况下应该 “失败”。
- 可失败构造器
Int(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)")
}
// 打印“12345.0 conversion to Int maintains value of 12345”
let valueChanged = Int(exactly: pi)
// valueChanged 是 Int? 类型,不是 Int 类型
if valueChanged == nil {
print("\(pi) conversion to Int does not maintain value")
}
// 打印“3.14159 conversion to Int does not maintain value”
- 自定义可失败构造器:
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty {
return nil
}
self.species = species
}
}
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”
注意:
- 可失败构造器的参数名和参数类型,不能与其它非可失败构造器的参数名,及其参数类型相同
- 严格来说,构造器都不支持返回值。因为构造器本身的作用,只是为了确保对象能被正确构造。因此你只是用
return nil
表明可失败构造器构造失败,而不要用关键字return
来表明构造成功
(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.”
(3)构造失败的传递
类、结构体、枚举的可失败构造器可以横向代理到它们自己其他的可失败构造器。类似的,子类的可失败构造器也能向上代理到父类的可失败构造器。
无论是向上代理还是横向代理,如果你代理到的其他可失败构造器触发构造失败,整个构造过程将立即终止,接下来的任何构造代码不会再被执行。
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
的构造方法能否构造成功有三种情况:
- 传入一个非空字符串
name
以及一个值大于等于 1 的quantity
,此时构造成功
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印“Item: sock, quantity: 2”
- 传入一个值为
0
的quantity
,构造失败,而且不会执行Product
的构造方法
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
,父类Product
构造过程失败,导致CartItem
构造失败
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)重写一个可失败构造器
如同其它的构造器,你可以在子类中重写父类的可失败构造器。或者你也可以用子类的非可失败构造器重写一个父类的可失败构造器。这使你可以定义一个不会构造失败的子类,即使父类的构造器允许构造失败。
注意,当你用子类的非可失败构造器重写父类的可失败构造器时,向上代理到父类的可失败构造器的唯一方式是对父类的可失败构造器的返回值进行强制解包。
class Document {
var name: String?
// 该构造器创建了一个 name 属性的值为 nil 的 document 实例
init() {}
// 该构造器创建了一个 name 属性的值为非空字符串的 document 实例
init?(name: String) {
if name.isEmpty { return nil }
self.name = 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
}
}
}
可以在子类的不可失败构造器中使用强制解包来调用父类的可失败构造器:
class UntitledDocument: Document {
override init() {
super.init(name: "[Untitled]")!
}
}
在这个例子中,如果在调用父类的可失败构造器 init?(name:)
时传入的是空字符串,那么强制解包操作会引发运行时错误。不过,因为这里是通过字符串常量来调用它,构造器不会失败,所以并不会发生运行时错误。
(5)init! 可失败构造器
通常来说我们通过在 init
关键字后添加问号的方式(init?
)来定义一个可失败构造器,但你也可以通过在 init
后面添加感叹号的方式来定义一个可失败构造器(init!
),该可失败构造器将会构建一个对应类型的隐式解包可选类型的对象。
你可以在 init?
中代理到 init!
,反之亦然。你也可以用 init?
重写 init!
,反之亦然。你还可以用 init
代理到 init!
,不过,一旦 init!
构造失败,则会触发一个断言。
7、必要构造器
在类的构造器前添加 required
修饰符表明所有该类的子类都必须实现该构造器:
class SomeClass {
required init() {
// 构造器的实现代码
}
}
在子类重写父类的必要构造器时,必须在子类的构造器前也添加 required
修饰符,表明该构造器要求也应用于继承链后面的子类。在重写父类中必要的指定构造器时,不需要添加 override
修饰符:
class SomeSubclass: SomeClass {
required init() {
// 构造器的实现代码
}
}
注意:如果子类继承的构造器能满足必要构造器的要求,则无须在子类中显式提供必要构造器的实现
8、通过闭包或函数设置属性的默认值
如果某个存储型属性的默认值需要一些自定义或设置,你可以使用闭包或全局函数为其提供定制的默认值。每当某个属性所在类型的新实例被构造时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。
用闭包为属性提供默认值:
class SomeClass {
let someProperty: SomeType = {
// 在这个闭包中给 someProperty 创建一个默认值
// someValue 必须和 SomeType 类型相同
return someValue
}()
}
闭包结尾的花括号后面接了一对空的小括号。这用来告诉 Swift 立即执行此闭包。如果你忽略了这对括号,相当于将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
结构体 Chessboard
,它构建了西洋跳棋游戏的棋盘,西洋跳棋游戏在一副黑白格交替的 8 x 8 的棋盘中进行的:
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]
}
}
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// 打印“true”
print(board.squareIsBlackAt(row: 7, column: 7))
// 打印“false”