Gather ye rosebuds while ye may

「翻译」 Swift 类型转换


这是对 swift 2.2 中 类型转换 一节的翻译
第一次翻译这么长的。。。好麻烦

类型转换

类型转换可以判断实例的类型,也可以将该实例在其所在的类层次中视为其父类或子类的实例。

Swift 中的类型转换使用 isas 作为操作符。这两个操作符使用了一种简单传神的方式来检查一个值的类型或将某个值转换为另一种类型。

你还可以使用类型转换来检查类型是否符合一个协议,参考协议实现的检查

为类型转换定义类层次

你可以在类及其子类层次中使用类型转换判断特定类实例的类型,或在同一类层次中将该实例类型转换为另一类型。下面的三段代码定义了一个类层次以及一个包含了这些类实例的数组,作为类型转换的例子。

第一个代码片段定义了一个叫做 MediaItem 的新类。这个类为出现在数字媒体库中的所有成员提供了基本的功能。它申明了一个 String 类型的 name 和一个叫做 initname 生成化器(这里假设所有的媒体项目,包括所有电影和音乐,都有一个名字)。

1
2
3
4
5
6
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}

下一个片段定义了两个 MediaItem 的子类。第一个子类 Movie 封装了电影的信息。他在 MediaItem 的基础上添加了名为 director 的属性及其生成化器。第二个子类 Song 增加了名为 artist 的属性及其生成化器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}

class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}

最后一个代码段创建了名为 library 的数组常量,其中包含了两个 Movie 和三个 Song 实例。library 数组的类型是在初始化时根据其中内容推断出来的。 Swift 的类型检查器能够推断 MovieSong 有一个共同的父类 MediaItem, 因此 library 的类型推断为 [MediaItem]

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")
]
// the type of "library" is inferred to be [MediaItem]

事实上 library 储存的项目依旧是 MovieSong 实例。然而,如果你遍历这个数组的内容,你取出的项目将会是 MediaItem 类型而非 MovieSong 类型。为了使用他们原生的类型,你需要检查他们的类型或将他们向下转换为不同的类型,如下所述。

检查类型

使用类型检查操作符 is 来检查一个实例是否属于一个特定的子类。如果实例属于该子类,类型检查操作符返回 true,否则返回 false

下面的例子定义了两个变量,movieCountsongCount,用来计算数组 libraryMovieSong 实例的个数:

1
2
3
4
5
6
7
8
9
10
11
12
13
var movieCount = 0
var songCount = 0

for item in library {
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}

print("Media library contains \(movieCount) movies and \(songCount) songs")
// Prints "Media library contains 2 movies and 3 songs

这个例子遍历了整个 library 数组。每一轮中,for-in 的循环都将 item 设置为数组中的当前 MediaItem

如果当前 MediaItemMovie 类型的实例,item is Movie 返回 true,反之返回 false。同样的,item is Song 检查了该对象是否为 Song 类型的实例。在 for-in 循环的最后,movieCountsongCount 的值就是数组中对应类型实例的数量。

向下类型转换

某个类型的常量或变量可能引用自一个子类的实例。当你遇到这种情况时你可以尝试使用类型转换操作符as?as!)将它向下转换类型至其子类型。

由于向下类型转换有可能失败,类型转换操作符有两个不同形式。条件形式 as? 返回了一个包含你将要向下转换类型的可选类型。强制形式 as! 则将向下转换和强制解包结合为一个步骤。

如果你不确定你向下转换类型是否成功,请使用条件形式的类型转换操作符 as?。使用条件形式的类型转换操作符总是返回一个可选类型,如果向下转换失败,可选值为 nil。可以使用这种方法检查向下类型转换是否成功。

当你确信向下转换类型会成功时,请使用强制形式的类型转换操作符 as!。当你向下转换至一个错误的类型时,强制形式的类型转换操作符会触发一个运行错误。

下面的例子遍历了 library 中所有的 MediaItem,并打印出相应的描述信息。要这样做的话,每个项目均需要被当做 MovieSong 类型来访问,而不仅仅是 MediaItem 类型。为了在描述信息中使用 MovieSongdirectorartist 属性,这样做是必要的。

在这个例子中,数组中每一个项目的类型都是 MovieSong。你不知道遍历时项目的确切类型是什么,所以这时使用条件形式的类型转换符(as?)来检查遍历中每次向下类型转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
for item in library {
if let movie = item as? Movie {
print("Movie: '\(movie.name)', dir. \(movie.director)")
} else if let 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

例子开头尝试将当前 item 当做 Movie 向下转换类型。由于 item 是一个 MediaItem 的实例,它有可能属于 Movie 类型;同样的,也有可能属于 Song 或者仅仅属于 MediaItem 基类。介于这种不确定性,类型转换符 as? 在向下转换类型时返回了一个可选项。item as? Movie 的结果是 Movie? 类型,也就是『可选 Movie 类型』。

当数组中的 Song 实例使用向下转换至 Movie 类型时会失败。为了处理这种情况,上面的例子使用了可选绑定来检查可选 Movie 类型是否包含了一个值(或者说检查向下类型转换是否成功)。这个可选绑定写作『if let movie = item as? Movie』,读作『尝试将 item 转换至 Movie 类型。如果成功,设置一个新的临时常量 movie 储存返回的可选 Movie 类型 』。

如果向下转换类型成功,movie 的属性将用于输出 Movie 实例的描述信息,包括director 的名字。同理,无论是否在数组中找到 Song ,均可以检查 Song 实例然后输出合适的描述(包括 artist 的名字)。

注意
类型转换不会改变实例及其值。基本的实例不会改变;只是将它当做其转换的类型来访问。

Any 和 AnyObject 的类型转换

Swift 为不确定的类型提供了两种特殊的类型别名:

  • AnyObject 可以表示任何类类型的实例。
  • Any 可以表示任何类型,包括函数类型。

注意
当你确切需要使用一个功能时再使用 AnyAnyObject。在写代码时使用更加明确的类型表达总要好一些。

AnyObject 类型

使用 Cocoa APIs 时,你有时会接收到 [AnyObject] 类型的数组,也就是『由任意类型对象构成的数组』。目前 Objective-C 支持显式类型的数组,但是旧版本的语言没有这个功能。然而你仍可以确信这种提供 API 信息的数组中包含的对象的类型。

在这种情况中,你可以使用强制类型转换符(as!)将数组中的项目向下转换至比 AnyObject 更明确的类型,而无需将可选类型解包。

下面的例子定义了一个 [AnyObject] 类型的数组,并实例化了三个 Movie 类:

1
2
3
4
5
let someObjects: [AnyObject] = [
Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
Movie(name: "Moon", director: "Duncan Jones"),
Movie(name: "Alien", director: "Ridley Scott")
]

由于我们已知这个数组只包含了 Movie 实例,你可以直接使用强制类型的类型转换符(as!)将其向下转换至非可选的 Movie 类型:

1
2
3
4
5
6
7
for object in someObjects {
let movie = object as! Movie
print("Movie: '\(movie.name)', dir. \(movie.director)")
}
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
// Movie: 'Moon', dir. Duncan Jones
// Movie: 'Alien', dir. Ridley Scott

在这个循环中,直接将 someObject 向下转换为 [Movie] 类型而非挨个向下转换,可以使代码更短:

1
2
3
4
5
6
for movie in someObjects as! [Movie] {
print("Movie: '\(movie.name)', dir. \(movie.director)")
}
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
// Movie: 'Moon', dir. Duncan Jones
// Movie: 'Alien', dir. Ridley Scott

Any 类型

这里有一个使用 Any 类型来对不同类型进行操作的例子,包含了函数类型以及非类类型。这个例子定义了一个名为 things 的数组用于储存 Any 类型的值:

1
2
3
4
5
6
7
8
9
10
var things = [Any]()

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })

这个 things 数组包含了两个 Int 值、两个 Double 值、一个 String 值、一个 (Double, Double) 的元组、Movie 实例『Ghostbusters』、以及一个接收 String 值并返回 String 值得闭包表达式。

你可以在 switch 结构的 case 中使用 isas 操作符找出已知 Any 或 AnyObject 类型的常量或变量的具体类型。下面的例子使用 switch 语句遍历了 things 数组并查询每一项的类型。其中几个 switch 的 cases 将确定的值和确定类型的常量绑定在一起,使其值可以被输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
for thing in things {
switch thing {
case 0 as Int:
print("zero as an Int")
case 0 as Double:
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let movie as Movie:
print("a movie called '\(movie.name)', dir. \(movie.director)")
case let 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