18 Type Casting 类型转换
类型转换是检查实例类型的一种方法,或将该实例视为与其自身类层次结构中其他位置不同的超类或子类。
Swift中的类型转换使用is和as操作符实现。这两个操作符提供了一种简单而富有表现力的方法来检查值的类型或将值转换为不同的类型。
您还可以使用类型转换来检查类型是否符合协议,请参考 Checking for Protocol Conformance。
Defining a Class Hierarchy for Type Casting 为类型转换定义类层次结构
您可以使用带有类和子类层次结构的类型转换来检查特定类实例的类型,并将该实例转换为同一层次结构中的另一个类。下面的三个代码片段定义了类的层次结构和包含这些类实例的数组,用于类型转换的示例。
第一个代码片段定义了一个名为MediaItem的新基类。该类为数字媒体库中出现的任何类型的项提供基本功能。具体来说,它声明一个String类型的name属性和一个init名称初始化器。(假设所有媒体项目,包括所有电影和歌曲,都有名称。)
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
下一个代码片段定义了MediaItem的两个子类。第一个子类Movie封装了关于一个或多个电影的附加信息。它在基本MediaItem类的顶部添加了一个director属性,并带有相应的初始化器。第二个子类Song在基类之上添加了艺术家属性和初始化器:
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的常量数组,其中包含两个电影实例和三个歌曲实例。库数组的类型通过使用数组文字的内容初始化它来推断。Swift的类型检查器能够推断出电影和歌曲有一个公共的MediaItem超类,因此它推断出library数组的[MediaItem]类型:
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]
库中存储的项目仍然是幕后的电影和歌曲实例。但是,如果您遍历这个数组的内容,则返回的项将被键入为MediaItem,而不是Movie或Song。为了将它们作为原生类型使用,您需要检查它们的类型,或者将它们向下转换为另一种类型,如下所述。
Checking Type
使用类型检查操作符(is)检查实例是否属于某个子类类型。如果实例属于该子类类型,则类型检查操作符返回true,如果不是,则返回false。
下面的例子定义了两个变量movieCount和songCount,它们计算库数组中Movie和Song实例的数量:
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"
注意,实际上并只是子类,你若是用父类MeidaItem来创建,一样可以检查。只是子类的检查在实际场景中使用更多。
let dd = MediaItem("低音炮")
if dd is MediaItem {
print("这是一个低音炮")
}
Downcasting 向下类型转换
某个确定类型的常量或变量实际上可能引用子类的实例。如果您认为是这种情况,您可以尝试使用类型转换操作符(as?或者as!)
由于向下强制转换可能失败,类型强制转换操作符有两种不同的形式。
- 条件式,as?,返回要向下强制转换为的类型的可选值。
- 强迫形式,as!,尝试向下强制转换并将结果作为单个复合操作展开。
当不确定向下转换是否成功时,使用类型转换操作符的条件形式(as?)。这种形式的操作符将始终返回一个可选值,如果无法向下强制转换,则该值将为nil。这使您能够检查是否成功向下强制转换。
只有在确信向下强制转换将始终成功时,才使用类型强制转换操作符(as!)的形式。如果您试图向下强制转换为不正确的类类型,这种形式的操作符将触发运行时错误。
下面的示例遍历库中的每个媒体项,并为每个项打印适当的描述。要做到这一点,它需要将每个条目作为一个真正的电影或歌曲来访问,而不仅仅是作为一个媒体条目。为了能够访问用于描述的电影或歌曲的导演或艺术家属性,这是必要的。
在本例中,数组中的每个项可能是电影,也可能是歌曲。您事先不知道为每个项目使用哪个实际类,因此,在每次循环中使用类型转换操作符(as?)的条件形式来检查向下转换是合适的:
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? 类型
当应用于库数组中的Song实例时,向下转换到Movie失败。为了处理这个问题,上面的示例使用可选绑定来检查可选影片是否实际包含一个值(也就是说,确定向下转换是否成功)。这个可选绑定被写为“if let movie = item as? Movie”,可读作:
“试着以Movie的形式访问item。如果成功,请将一个名为movie的新临时常量设置为存储在返回的可选movie中的值。”
如果向下转换成功,则使用movie的属性打印该movie实例的描述,包括其导演的名称。类似的原则用于检查歌曲实例,并在库中找到歌曲时打印适当的描述(包括歌手名)。
强制转换实际上并不修改实例或更改其值。底层实例保持不变;它只是作为类型的一个实例来处理和访问。
Type Casting for Any and AnyObject 为任意和任意对象类型转换
Swift为处理非特定类型提供了两种特殊类型:
- Any 可以表示任何类型的实例,包括函数类型。
- AnyObject 可以表示任何 class 类型的实例。
只有在显式地需要Any和AnyObject提供的行为和功能时才使用它们。在代码中指定希望使用的类型总是更好的。
下面是一个使用Any来处理不同类型(包括函数类型和非类类型)的混合的例子。这个例子创建了一个名为things的数组,它可以存储任意类型的值:
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)" })
要发现只有Any或AnyObject类型的常量或变量的特定类型,可以在switch语句的案例中使用is或as模式。下面的示例遍历things数组中的项,并使用switch语句查询每个项的类型。switch语句的一些用例将它们匹配的值绑定到指定类型的常量,以使其值能够打印:
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
Any类型表示任何类型的值,包括可选类型。如果您使用的是一个可选值,而值的类型是Any, Swift会给您一个警告。如果确实需要将可选值用作Any值,可以使用as操作符显式地将可选值转换为Any,如下所示。
let optionalNumber: Int? = 3
things.append(optionalNumber) // Warning
things.append(optionalNumber as Any) // No warning
注意这里是黄色警告,不是红色错误,但我们要尽量避免黄色警告,实际上还可以使用如下两种方式(推荐使用 as Any):
things.append(optionalNumber ?? 3) // No warning
things.append(optionalNumber!) // No warning