第十四章 构造
6.4 Overriding a Failable Initializer (重写一个失败构造器)
我们可以在一个子类里面重写父类的失败构造器,就像重写其他构造器那样,这样以来可以用子类的非失败构造器来重写父类里面的失败构造器。要想重写一个失败构造器,那么我们就不能在定义子类里面包含一个失败构造器。因为我们要用子类里面的非失败构造器来重写父类里面的构造器呢。需要注意的是用子类里面的非失败构造器重写一个父类里面的失败构造器的时候,要向上代理父类里买呢的构造器,就只能用强制展开父类里面失败构在器的结果。
下面这个例子定义来一个类(父类)Document
。这个类有一个name
的属性。并且这个属性的值必须是一个非空(nonempty
)的String值或者是nil
的值。不可以是一个空白值(empty value)。这是前提的条件 如果想要在子类的非失败构造器里面重写父类的失败构造器。The example below defines a class called Document. This class models a document that can be initialized with a name property that is either a nonempty string value or nil, but cannot be an empty string:
class Document {
var name: String?
init() {}
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 {
// 没名称用Untitled
self.name = "[Untitled]"
} else {
// 有名称用名称
self.name = name
}
}
}
在父类的第一个子类里面我们已经对父类的失败构造器进行了重写,构造好了Document
有名称和没名称的整个构造过程。在父类的另一个子类里面的非失败构造器来重写调用父类里面的可失败构造器的名字,进而通过重写来强制展开name
里面的属性值,所以现在该子类里面的的UntitledDocument
名字一直是[Untitled]
。
class UntitledDocument: Document {
override init() {
super.init(name: "[Untitled]")!
}
}
在上面的案例里面,父类里面的init(name: )一直以空白值作为名字来调用。这个强制展开的操作将会在运行的时候出现runtime error。父类的构造器会失败因为。因为我们一直以常量String值来调用的这个构造器。在这个案例里面则不会出现runtime error。
6.5 The init! Failable Initializer (失败构造器 init! )
通常情况下以在init
关键字后面添加一个?
的形式来定义一个来创建某个类型的可选实例的失败构造器。还有一种就是直接init
关键字后面添加一个!
的形式来表达这个失败构造器,它会隐含式的展开一个合适类型的可选实例。
你可以在init?
中代理到init!
,你也可以用init?
重写init!
,你还可以用init
代理 到init!
,不过,一旦init!
构造失败,则会触发一个断言。
7. Required Initializers (必要构造器)
我们可以在类的构造器前面添加required
修饰符,来用于说明这个类下面的任何一个子类都要实现这个构造器。
class SomeClass {
required init() {
// initializer implementation goes here
}
}
同样的 也可以给子类的构造器前面添加required
修饰符,用来说明该子类的子类要实现这个构造器。但是不能用override修饰符来重写必要指定构造器。
class SomeSubclass: SomeClass {
required init() {
// subclass implementation of the required initializer goes here
}
}
8. Setting a Default Property Value with a Closure or Function (用闭包或函数来设置默认值)
我们可以用闭包或全局函数来为某个属性提供一个自定义的默认值,当存储属性的默认值需要一些设置或自定义的时候。调用这个函数或闭包的时候,它们的返回值就可以用来作为属性的默认值。
这种类型的闭包或函数通常会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后返回这个临时变量,作为属性的默认值。
下面介绍了如何用闭包为属性提供默认值:
class SomeClass {
let someProperty: SomeType = {
// create a default value for someProperty inside this closure
// someValue must be of the same type as SomeType
return someValue
}()
}
上面这个案例中需要注意的是闭包
和后面空白的小括号
,这种写法会直接告诉Swift来立即执行这个闭包。如果我们省略处理后面的小括号,那就意味着我们要把这个要执行返回值的闭包看作是这个class的一个属性。
下面这个是西洋跳棋的游戏。这个例子定义来一个结构体Chessboard
,西洋棋在一个8X8的黑白格子上玩的。
为了展示这个游戏,该结构体Chessboard的属性boardColors是64个布尔值的数字。一个黑格子表示一个True
的值,一个白格子是False
的值,数组里面的第一个item代表着棋盘做左上角的格子,最后一个Item代表着最右下的格子。
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]
}
}
每当一个结构体的实例被创建的时候,这个闭包就会执行,并且计算默认值和发挥默认值。闭包在上面这个例子里面会计算出格子岁对应的颜色,将这些计算出来的颜色值保存到一个叫temporaryBoard
的临时数组中,最后呢返回这个临时数组作为闭包的返回值,返回的函数会保存到boardColors
里面。并且还可以用squareIsBlackAt(row:column:)
通过工具函数(utility function)来查询这些保存到boardColors里面的值。
let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// 输出:true
print(board.squareIsBlackAt(row: 7, column: 7))
// 输出:false