上述代码实现了“相等”运算符(==)来判断两个 Vector2D 实例是否相等。对于 Vector2D 来说,“相等”意味着“两个实例的 x 和 y 都相等”,这也是代码中用来进行判等的逻辑。如果你已经实现了“相等”运算符,通常情况下你并不需要自己再去实现“不等”运算符(!=)。标准库对于“不等”运算符提供了默认的实现,它简单地将“相等”运算符的结果进行取反后返回。
现在我们可以使用这两个运算符来判断两个 Vector2D 实例是否相等:
1 2 3 4 5 6
let twoThree =Vector2D(x: 2.0, y: 3.0) let anotherTwoThree =Vector2D(x: 2.0, y: 3.0) if twoThree == anotherTwoThree { print("These two vectors are equivalent.") } // 打印“These two vectors are equivalent.”
多数简单情况下,您可以使用 Swift 为您提供的等价运算符默认实现。Swift 为以下数种自定义类型提供等价运算符的默认实现:
只拥有存储属性,并且它们全都遵循 Equatable 协议的结构体
只拥有关联类型,并且它们全都遵循 Equatable 协议的枚举
没有关联类型的枚举
在类型原始的声明中声明遵循 Equatable 来接收这些默认实现。
下面为三维位置向量 (x, y, z) 定义的 Vector3D 结构体,与 Vector2D 类似。由于 x,y 和 z 属性都是 Equatable 类型,Vector3D 获得了默认的等价运算符实现。
1 2 3 4 5 6 7 8 9 10
structVector3D: Equatable { var x =0.0, y =0.0, z =0.0 }
let twoThreeFour =Vector3D(x: 2.0, y: 3.0, z: 4.0) let anotherTwoThreeFour =Vector3D(x: 2.0, y: 3.0, z: 4.0) if twoThreeFour == anotherTwoThreeFour { print("These two vectors are also equivalent.") } // 打印“These two vectors are also equivalent.”
自定义运算符
除了实现标准运算符,在 Swift 中还可以声明和实现自定义运算符。可以用来自定义运算符的字符列表请参考 运算符。
这个运算符把两个向量的 x 值相加,同时从第一个向量的 y 中减去第二个向量的 y 。因为它本质上是属于“相加型”运算符,所以将它放置在 + 和 - 等默认中缀“相加型”运算符相同的优先级组中。关于 Swift 标准库提供的运算符,以及完整的运算符优先级组和结合性设置,请参考 运算符声明。而更多关于优先级组以及自定义操作符和优先级组的语法,请参考 运算符声明。
上面提到,一个 public 类型的所有成员的访问级别默认为 internal 级别,而不是 public 级别。如果你想将某个成员指定为 public 级别,那么你必须显式指定。这样做的好处是,在你定义公共接口的时候,可以明确地选择哪些接口是需要公开的,哪些是内部使用的,避免不小心将内部使用的接口公开。
如果你实例化 TrackedString 结构体,并多次对 value 属性的值进行修改,你就会看到 numberOfEdits 的值会随着修改次数而变化:
1 2 3 4 5 6
var stringToEdit =TrackedString() stringToEdit.value ="This string will be tracked." stringToEdit.value +=" This edit will increment numberOfEdits." stringToEdit.value +=" So will this one." print("The number of edits is \(stringToEdit.numberOfEdits)") // 打印“The number of edits is 3”
默认构造器的访问级别与所属类型的访问级别相同,除非类型的访问级别是 public。如果一个类型被指定为 public 级别,那么默认构造器的访问级别将为 internal。如果你希望一个 public 级别的类型也能在其他模块中使用这种无参数的默认构造器,你只能自己提供一个 public 访问级别的无参数构造器。
你定义的任何类型别名都会被当作不同的类型,以便于进行访问控制。类型别名的访问级别不可高于其表示的类型的访问级别。例如,private 级别的类型别名可以作为 private、file-private、internal、public 或者 open 类型的别名,但是 public 级别的类型别名只能作为 public 类型的别名,不能作为 internal、file-private 或 private 类型的别名。
Swift 也保证同时访问同一块内存时不会冲突,通过约束代码里对于存储地址的写操作,去获取那一块内存的访问独占权。因为 Swift 自动管理内存,所以大部分时候你完全不需要考虑内存访问的事情。然而,理解潜在的冲突也是很重要的,可以避免你写出访问冲突的代码。而如果你的代码确实存在冲突,那在编译时或者运行时就会得到错误。
理解内存访问冲突
内存的访问,会发生在你给变量赋值,或者传递参数给函数时。例如,下面的代码就包含了读和写的访问:
1 2 3 4 5
// 向 one 所在的内存区域发起一次写操作 var one =1
// 向 one 所在的内存区域发起一次读操作 print("We're number \(one)!")
内存访问的冲突会发生在你的代码尝试同时访问同一个存储地址的时侯。同一个存储地址的多个访问同时发生会造成不可预计或不一致的行为。在 Swift 里,有很多修改值的行为都会持续好几行代码,在修改值的过程中进行访问是有可能发生的。
funcbalance(_x: inoutInt, _y: inoutInt) { let sum = x + y x = sum /2 y = sum - x } var playerOneScore =42 var playerTwoScore =30 balance(&playerOneScore, &playerTwoScore) // 正常 balance(&playerOneScore, &playerOneScore) // 错误:playerOneScore 访问冲突
var oscar =Player(name: "Oscar", health: 10, energy: 10) var maria =Player(name: "Maria", health: 5, energy: 10) oscar.shareHealth(with: &maria) // 正常
上面的例子里,调用 shareHealth(with:) 方法去把 oscar 玩家的血量分享给 maria 玩家并不会造成冲突。在方法调用期间会对 oscar 发起写访问,因为在 mutating 方法里 self 就是 oscar,同时对于 maria 也会发起写访问,因为 maria 作为 in-out 参数传入。过程如下,它们会访问内存的不同位置。即使两个写访问重叠了,它们也不会冲突。
funcsomeFunction() { var oscar =Player(name: "Oscar", health: 10, energy: 10) balance(&oscar.health, &oscar.energy) // 正常 }
上面的例子里,oscar 的 health 和 energy 都作为 in-out 参数传入了 balance(_:_:) 里。编译器可以保证内存安全,因为两个存储属性任何情况下都不会相互影响。
限制结构体属性的重叠访问对于保证内存安全不是必要的。保证内存安全是必要的,但因为访问独占权的要求比内存安全还要更严格——意味着即使有些代码违反了访问独占权的原则,也是内存安全的,所以如果编译器可以保证这种非专属的访问是安全的,那 Swift 就会允许这种行为的代码运行。特别是当你遵循下面的原则时,它可以保证结构体属性的重叠访问是安全的:
下面的例子展示了自动引用计数的工作机制。例子以一个简单的 Person 类开始,并定义了一个叫 name 的常量属性:
1 2 3 4 5 6 7 8 9 10
classPerson { let name: String init(name: String) { self.name = name print("\(name) is being initialized") } deinit { print("\(name) is being deinitialized") } }
Person 类有一个构造器,此构造器为实例的 name 属性赋值,并打印一条消息以表明初始化过程生效。Person 类也拥有一个析构器,这个析构器会在实例被销毁时打印一条消息。
接下来的代码片段定义了三个类型为 Person? 的变量,用来按照代码片段中的顺序,为新的 Person 实例建立多个引用。由于这些变量是被定义为可选类型(Person?,而不是 Person),它们的值会被自动初始化为 nil,目前还不会引用到 Person 类的实例。
1 2 3
var reference1: Person? var reference2: Person? var reference3: Person?
现在你可以创建 Person 类的新实例,并且将它赋值给三个变量中的一个:
1 2
reference1 =Person(name: "John Appleseed") // 打印“John Appleseed is being initialized”
应当注意到当你调用 Person 类的构造器的时候,"John Appleseed is being initialized" 会被打印出来。由此可以确定构造器被执行。
由于 Person 类的新实例被赋值给了 reference1 变量,所以 reference1 到 Person 类的新实例之间建立了一个强引用。正是因为这一个强引用,ARC 会保证 Person 实例被保持在内存中不被销毁。
classPerson { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { print("\(name) is being deinitialized") } }
classApartment { let unit: String init(unit: String) { self.unit = unit } var tenant: Person? deinit { print("Apartment \(unit) is being deinitialized") } }
每一个 Person 实例有一个类型为 String,名字为 name 的属性,并有一个可选的初始化为 nil 的 apartment 属性。apartment 属性是可选的,因为一个人并不总是拥有公寓。
下面的例子定义了两个类,Country 和 City,每个类将另外一个类的实例保存为属性。在这个模型中,每个国家必须有首都,每个城市必须属于一个国家。为了实现这种关系,Country 类拥有一个 capitalCity 属性,而 City 类有一个 country 属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
classCountry { let name: String var capitalCity: City! init(name: String, capitalName: String) { self.name = name self.capitalCity =City(name: capitalName, country: self) } }
classCity { let name: String unownedlet country: Country init(name: String, country: Country) { self.name = name self.country = country } }
为了建立两个类的依赖关系,City 的构造器接受一个 Country 实例作为参数,并且将实例保存到 country 属性。
Country 的构造器调用了 City 的构造器。然而,只有 Country 的实例完全初始化后,Country 的构造器才能把 self 传给 City 的构造器。在 两段式构造过程 中有具体描述。
为了满足这种需求,通过在类型结尾处加上感叹号(City!)的方式,将 Country 的 capitalCity 属性声明为隐式解包可选值类型的属性。这意味着像其他可选类型一样,capitalCity 属性的默认值为 nil,但是不需要展开它的值就能访问它。在 隐式解包可选值 中有描述。
由于 capitalCity 默认值为 nil,一旦 Country 的实例在构造器中给 name 属性赋值后,整个初始化过程就完成了。这意味着一旦 name 属性被赋值后,Country 的构造器就能引用并传递隐式的 self。Country 的构造器在赋值 capitalCity 时,就能将 self 作为参数传递给 City 的构造器。
以上的意义在于你可以通过一条语句同时创建 Country 和 City 的实例,而不产生循环强引用,并且 capitalCity 的属性能被直接访问,而不需要通过感叹号来展开它的可选值:
1 2 3
var country =Country(name: "Canada", capitalName: "Ottawa") print("\(country.name)'s capital city is called \(country.capitalCity.name)") // 打印“Canada's capital city is called Ottawa”
泛型是 Swift 最强大的特性之一,很多 Swift 标准库是基于泛型代码构建的。实际上,即使你没有意识到,你也一直在语言指南中使用泛型。例如,Swift 的 Array 和 Dictionary 都是泛型集合。你可以创建一个 Int 类型数组,也可创建一个 String 类型数组,甚至可以是任意其他 Swift 类型的数组。同样,你也可以创建一个存储任意指定类型的字典,并对该类型没有限制。
泛型解决的问题
下面是一个标准的非泛型函数 swapTwoInts(_:_:),用来交换两个 Int 值:
1 2 3 4 5
funcswapTwoInts(_a: inoutInt, _b: inoutInt) { let temporaryA = a a = b b = temporaryA }
swapTwoInts(_:_:) 函数将 b 的原始值换成了 a,将 a 的原始值换成了 b,你可以调用这个函数来交换两个 Int 类型变量:
1 2 3 4 5
var someInt =3 var anotherInt =107 swapTwoInts(&someInt, &anotherInt) print("someInt is now \(someInt), and anotherInt is now \(anotherInt)") // 打印“someInt is now 107, and anotherInt is now 3”
泛型版本的函数使用占位符类型名(这里叫做 T ),而不是 实际类型名(例如 Int、String 或 Double),占位符类型名并不关心 T 具体的类型,但它要求 a 和b 必须是相同的类型,T 的实际类型由每次调用 swapTwoValues(_:_:) 来决定。
泛型函数和非泛型函数的另外一个不同之处在于这个泛型函数名(swapTwoValues(_:_:))后面跟着占位类型名(T),并用尖括号括起来(<T>)。这个尖括号告诉 Swift 那个 T 是 swapTwoValues(_:_:) 函数定义内的一个占位类型名,因此 Swift 不会去查找名为 T的实际类型。
funcfindIndex(ofStringvalueToFind: String, inarray: [String]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } returnnil }
findIndex(ofString:in:) 函数可以用于查找字符串数组中的某个字符串值:
1 2 3 4 5
let strings = ["cat", "dog", "llama", "parakeet", "terrapin"] iflet foundIndex = findIndex(ofString: "llama", in: strings) { print("The index of llama is \(foundIndex)") } // 打印“The index of llama is 2”
如果只能查找字符串在数组中的索引,用处不是很大。不过,你可以用占位类型 T 替换 String 类型来写出具有相同功能的泛型函数 findIndex(_:_:)。
funcfindIndex<T>(ofvalueToFind: T, inarray:[T]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } returnnil }
上面所写的函数无法通过编译。问题出在相等性检查上,即 “if value == valueToFind”。不是所有的 Swift 类型都可以用等式符(==)进行比较。例如,如果你自定义类或结构体来描述复杂的数据模型,对于这个类或结构体而言,Swift 无法明确知道“相等”意味着什么。正因如此,这部分代码无法保证适用于任意类型 T,当你试图编译这部分代码时就会出现相应的错误。
不过,所有的这些并不会让我们无从下手。Swift 标准库中定义了一个 Equatable 协议,该协议要求任何遵循该协议的类型必须实现等式符(==)及不等符(!=),从而能对该类型的任意两个值进行比较。所有的 Swift 标准类型自动支持 Equatable 协议。
funcfindIndex<T: Equatable>(ofvalueToFind: T, inarray:[T]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { return index } } returnnil }
extensionIntStack: SuffixableContainer { funcsuffix(_size: Int) -> Stack<Int> { var result =Stack<Int>() for index in (count-size)..<count { result.append(self[index]) } return result } // 推断 suffix 结果是 Stack<Int>。 }
对关联类型添加约束通常是非常有用的。你可以通过定义一个泛型 where 子句来实现。通过泛型 where 子句让关联类型遵从某个特定的协议,以及某个特定的类型参数和关联类型必须类型相同。你可以通过将 where 关键字紧跟在类型参数列表后面来定义 where 子句,where 子句后跟一个或者多个针对关联类型的约束,以及一个或多个类型参数和关联类型间的相等关系。你可以在函数体或者类型的大括号之前添加 where 子句。
if [9, 9, 9].startsWith(42) { print("Starts with 42.") } else { print("Starts with something else.") } // 打印“Starts with something else.”
上述示例中的泛型 where 子句要求 Item 遵循协议,但也可以编写一个泛型 where 子句去要求 Item 为特定类型。例如:
1 2 3 4 5 6 7 8 9 10 11
extensionContainerwhereItem==Double { funcaverage() -> Double { var sum =0.0 for index in0..<count { sum +=self[index] } return sum /Double(count) } } print([1260.0, 1200.0, 98.6, 37.0].average()) // 打印“648.9”
下标可以是泛型,它们能够包含泛型 where 子句。你可以在 subscript 后用尖括号来写占位符类型,你还可以在下标代码块花括号前写 where 子句。例如:
1 2 3 4 5 6 7 8 9 10
extensionContainer { subscript<Indices: Sequence>(indices: Indices) -> [Item] whereIndices.Iterator.Element==Int { var result = [Item]() for index in indices { result.append(self[index]) } return result } }
classLinearCongruentialGenerator: RandomNumberGenerator { var lastRandom =42.0 let m =139968.0 let a =3877.0 let c =29573.0 funcrandom() -> Double { lastRandom = ((lastRandom * a + c).truncatingRemainder(dividingBy:m)) return lastRandom / m } } let generator =LinearCongruentialGenerator() print("Here's a random number: \(generator.random())") // 打印 “Here's a random number: 0.37464991998171” print("And another one: \(generator.random())") // 打印 “And another one: 0.729023776863283”
var d6 =Dice(sides: 6, generator: LinearCongruentialGenerator()) for_in1...5 { print("Random dice roll is \(d6.roll())") } // Random dice roll is 3 // Random dice roll is 5 // Random dice roll is 4 // Random dice roll is 5 // Random dice roll is 4
classDiceGameTracker: DiceGameDelegate { var numberOfTurns =0 funcgameDidStart(_game: DiceGame) { numberOfTurns =0 if game isSnakesAndLadders { print("Started a new game of Snakes and Ladders") } print("The game is using a \(game.dice.sides)-sided dice") } funcgame(_game: DiceGame, didStartNewTurnWithDiceRolldiceRoll: Int) { numberOfTurns +=1 print("Rolled a \(diceRoll)") } funcgameDidEnd(_game: DiceGame) { print("The game lasted for \(numberOfTurns) turns") } }
gameDidStart(_:) 方法从 game 参数获取游戏信息并打印。game 参数是 DiceGame 类型而不是 SnakeAndLadders 类型,所以在 gameDidStart(_:) 方法中只能访问 DiceGame 协议中的内容。当然了,SnakeAndLadders 的方法也可以在类型转换之后调用。在上例代码中,通过 is 操作符检查 game 是否为 SnakesAndLadders 类型的实例,如果是,则打印出相应的消息。
无论当前进行的是何种游戏,由于 game 符合 DiceGame 协议,可以确保 game 含有 dice 属性。因此在 gameDidStart(_:) 方法中可以通过传入的 game 参数来访问 dice 属性,进而打印出 dice 的 sides 属性的值。
DiceGameTracker 的运行情况如下所示:
1 2 3 4 5 6 7 8 9 10 11
let tracker =DiceGameTracker() let game =SnakesAndLadders() game.delegate = tracker game.play() // Started a new game of Snakes and Ladders // The game is using a 6-sided dice // Rolled a 3 // Rolled a 5 // Rolled a 4 // Rolled a 5 // The game lasted for 4 turns
extensionSnakesAndLadders: TextRepresentable { var textualDescription: String { return"A game of Snakes and Ladders with \(finalSquare) squares" } } print(game.textualDescription) // 打印 “A game of Snakes and Ladders with 25 squares”
有条件地遵循协议
泛型类型可能只在某些情况下满足一个协议的要求,比如当类型的泛型形式参数遵循对应协议时。你可以通过在扩展类型时列出限制让泛型类型有条件地遵循某协议。在你采纳协议的名字后面写泛型 where 分句。更多关于泛型 where 分句,见 泛型 Where 分句。
structHamster { var name: String var textualDescription: String { return"A hamster named \(name)" } } extensionHamster: TextRepresentable {}
从现在起,Hamster 的实例可以作为 TextRepresentable 类型使用:
1 2 3 4
let simonTheHamster =Hamster(name: "Simon") let somethingTextRepresentable: TextRepresentable= simonTheHamster print(somethingTextRepresentable.textualDescription) // 打印 “A hamster named Simon”
for object in objects { iflet objectWithArea = object as?HasArea { print("Area is \(objectWithArea.area)") } else { print("Something that doesn't have an area") } } // Area is 12.5663708 // Area is 243610.0 // Something that doesn't have an area
let generator =LinearCongruentialGenerator() print("Here's a random number: \(generator.random())") // 打印 “Here's a random number: 0.37464991998171” print("And here's a random Boolean: \(generator.randomBool())") // 打印 “And here's a random Boolean: true”
扩展可以给现有类型添加计算型实例属性和计算型类属性。这个例子给 Swift 内建的 Double 类型添加了五个计算型实例属性,从而提供与距离单位相关工作的基本支持:
1 2 3 4 5 6 7 8 9 10 11 12 13
extensionDouble { var km: Double { returnself*1_000.0 } var m: Double { returnself } var cm: Double { returnself/100.0 } var mm: Double { returnself/1_000.0 } var ft: Double { returnself/3.28084 } } let oneInch =25.4.mm print("One inch is \(oneInch) meters") // 打印“One inch is 0.0254 meters” let threeFeet =3.ft print("Three feet is \(threeFeet) meters") // 打印“Three feet is 0.914399970739201 meters”
// 嵌套的 Suit 枚举 enumSuit: Character { case spades ="♠", hearts ="♡", diamonds ="♢", clubs ="♣" }
// 嵌套的 Rank 枚举 enumRank: Int { case two =2, three, four, five, six, seven, eight, nine, ten case jack, queen, king, ace structValues { let first: Int, second: Int? } var values: Values { switchself { case .ace: returnValues(first: 1, second: 11) case .jack, .queen, .king: returnValues(first: 10, second: nil) default: returnValues(first: self.rawValue, second: nil) } } }
// BlackjackCard 的属性和方法 let rank: Rank, suit: Suit var description: String { var output ="suit is \(suit.rawValue)," output +=" value is \(rank.values.first)" iflet second = rank.values.second { output +=" or \(second)" } return output } }
Suit 枚举用来描述扑克牌的四种花色,并用一个 Character 类型的原始值表示花色符号。
Rank 枚举用来描述扑克牌从 Ace~10,以及 J、Q、K,这 13 种牌,并用一个 Int 类型的原始值表示牌的面值。(这个 Int 类型的原始值未用于 Ace、J、Q、K 这 4 种牌。)
let theAceOfSpades =BlackjackCard(rank: .ace, suit: .spades) print("theAceOfSpades: \(theAceOfSpades.description)") // 打印“theAceOfSpades: suit is ♠, value is 1 or 11”
第一个代码片段定义了一个新的基类 MediaItem。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 String 类型的 name 属性,和一个 init(name:) 初始化器。(假定所有的媒体项都有个名称。)
1 2 3 4 5 6
classMediaItem { var name: String init(name: String) { self.name = name } }
下一个代码段定义了 MediaItem 的两个子类。第一个子类 Movie 封装了与电影相关的额外信息,在父类(或者说基类)的基础上增加了一个 director(导演)属性,和相应的初始化器。第二个子类 Song,在父类的基础上增加了一个 artist(艺术家)属性,和相应的初始化器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
classMovie: MediaItem { var director: String init(name: String, director: String) { self.director = director super.init(name: name) } }
classSong: MediaItem { var artist: String init(name: String, artist: String) { self.artist = artist super.init(name: name) } }
最后一个代码段创建了一个数组常量 library,包含两个 Movie 实例和三个 Song 实例。library 的类型是在它被初始化时根据它数组中所包含的内容推断来的。Swift 的类型检测器能够推断出 Movie 和 Song 有共同的父类 MediaItem,所以它推断出 [MediaItem] 类作为 library 的类型:
1 2 3 4 5 6 7 8
let library = [ Movie(name: "Casablanca", director: "Michael Curtiz"), Song(name: "Blue Suede Shoes", artist: "Elvis Presley"), Movie(name: "Citizen Kane", director: "Orson Welles"), Song(name: "The One And Only", artist: "Chesney Hawkes"), Song(name: "Never Gonna Give You Up", artist: "Rick Astley") ] // 数组 library 的类型被推断为 [MediaItem]
在幕后 library 里存储的媒体项依然是 Movie 和 Song 类型的。但是,若你迭代它,依次取出的实例会是 MediaItem 类型的,而不是 Movie 和 Song 类型。为了让它们作为原本的类型工作,你需要检查它们的类型或者向下转换它们到其它类型,就像下面描述的一样。
若当前 MediaItem 是一个 Movie 类型的实例,item is Movie 返回 true,否则返回 false。同样的,item is Song 检查 item 是否为 Song 类型的实例。在循环结束后,movieCount 和 songCount 的值就是被找到的属于各自类型的实例的数量。
下面的例子,迭代了 library 里的每一个 MediaItem,并打印出适当的描述。要这样做,item 需要真正作为 Movie 或 Song 的类型来使用,而不仅仅是作为 MediaItem。为了能够在描述中使用 Movie 或 Song 的 director 或 artist 属性,这是必要的。
在这个示例中,数组中的每一个 item 可能是 Movie 或 Song。事前你不知道每个 item 的真实类型,所以这里使用条件形式的类型转换(as?)去检查循环里的每次下转:
1 2 3 4 5 6 7 8 9 10 11 12 13
for item in library { iflet movie = item as?Movie { print("Movie: \(movie.name), dir. \(movie.director)") } elseiflet song = item as?Song { print("Song: \(song.name), by \(song.artist)") } }
// Movie: Casablanca, dir. Michael Curtiz // Song: Blue Suede Shoes, by Elvis Presley // Movie: Citizen Kane, dir. Orson Welles // Song: The One And Only, by Chesney Hawkes // Song: Never Gonna Give You Up, by Rick Astley
当向下转型为 Movie 应用在两个 Song 实例时将会失败。为了处理这种情况,上面的例子使用了可选绑定(optional binding)来检查可选 Movie 真的包含一个值(这个是为了判断下转是否成功。)可选绑定是这样写的“if let movie = item as? Movie”,可以这样解读:
“尝试将 item 转为 Movie 类型。若成功,设置一个新的临时常量 movie 来存储返回的可选 Movie 中的值”
若向下转型成功,然后 movie 的属性将用于打印一个 Movie 实例的描述,包括它的导演的名字 director。相似的原理被用来检测 Song 实例,当 Song 被找到时则打印它的描述(包含 artist 的名字)。
注意
转换没有真的改变实例或它的值。根本的实例保持不变;只是简单地把它作为它被转换成的类型来使用。
Any 和 AnyObject 的类型转换
Swift 为不确定类型提供了两种特殊的类型别名:
Any 可以表示任何类型,包括函数类型。
AnyObject 可以表示任何类类型的实例。
只有当你确实需要它们的行为和功能时才使用 Any 和 AnyObject。最好还是在代码中指明需要使用的类型。
这里有个示例,使用 Any 类型来和混合的不同类型一起工作,包括函数类型和非类类型。它创建了一个可以存储 Any 类型的数组 things:
things 数组包含两个 Int 值,两个 Double 值,一个 String 值,一个元组 (Double, Double),一个 Movie 实例“Ghostbusters”,以及一个接受 String 值并返回另一个 String 值的闭包表达式。
你可以在 switch 表达式的 case 中使用 is 和 as 操作符来找出只知道是 Any 或 AnyObject 类型的常量或变量的具体类型。下面的示例迭代 things 数组中的每一项,并用 switch 语句查找每一项的类型。有几个 switch 语句的 case 绑定它们匹配到的值到一个指定类型的常量,从而可以打印这些值:
for thing in things { switch thing { case0asInt: print("zero as an Int") case0asDouble: print("zero as a Double") caselet someInt asInt: print("an integer value of \(someInt)") caselet someDouble asDoublewhere someDouble >0: print("a positive double value of \(someDouble)") caseisDouble: print("some other double value that I don't want to print") caselet someString asString: print("a string value of \"\(someString)\"") caselet (x, y) as (Double, Double): print("an (x, y) point at \(x), \(y)") caselet movie asMovie: print("a movie called \(movie.name), dir. \(movie.director)") caselet stringConverter as (String) -> String: print(stringConverter("Michael")) default: print("something else") } }
// zero as an Int // zero as a Double // an integer value of 42 // a positive double value of 3.14159 // a string value of "hello" // an (x, y) point at 3.0, 5.0 // a movie called Ghostbusters, dir. Ivan Reitman // Hello, Michael
注意
Any 类型可以表示所有类型的值,包括可选类型。Swift 会在你用 Any 类型来表示一个可选值的时候,给你一个警告。如果你确实想使用 Any 类型来承载可选值,你可以使用 as 操作符显式转换为 Any,如下所示:
funcnourish(withitem: String) throws { do { try vendingMachine.vend(itemNamed: item) } catchisVendingMachineError { print("Invalid selection, out of stock, or not enough money.") } }
do { try nourish(with: "Beet-Flavored Chips") } catch { print("Unexpected non-vending-machine-related error: \(error)") } // 打印“Invalid selection, out of stock, or not enough money.”
可以使用 try? 通过将错误转换成一个可选值来处理错误。如果是在计算 try? 表达式时抛出错误,该表达式的结果就为 nil。例如,在下面的代码中,x 和 y 有着相同的数值和等价的含义:
1 2 3 4 5 6 7 8 9 10 11 12
funcsomeThrowingFunction() throws -> Int { // ... }
let x =try? someThrowingFunction()
let y: Int? do { y =try someThrowingFunction() } catch { y =nil }
如果 someThrowingFunction() 抛出一个错误,x 和 y 的值是 nil。否则 x 和 y 的值就是该函数的返回值。注意,无论 someThrowingFunction() 的返回值类型是什么类型,x 和 y 都是这个类型的可选类型。例子中此函数返回一个整型,所以 x 和 y 是可选整型。