类型转换是一种检查实例类型的方法,或者在类层次结构的其他地方将该实例视为不同的超类或子类。
在 Swift 中,类型转换通过 is
和 as
运算符实现。这两个运算符提供了一种简单且表达力强的方式来检查值的类型或将值转换为不同的类型。
您还可以使用类型转换来检查类型是否符合协议,如 [Checking for Protocol Conformance] 所述。
为类型转换定义类层次结构
您可以使用类层次结构和子类进行类型转换,检查特定类实例的类型,并将该实例转换为同一层次结构内的另一个类。下面的三个代码片段定义了一个类层次结构和包含这些类实例的数组,用于类型转换示例。
第一个片段定义了一个新的基类 MediaItem
。该类为数字媒体库中出现的任何类型项目提供了基本功能。具体来说,它声明了一个 name
属性,类型为 String
,以及一个 init(name:)
初始化器。(假设所有媒体项目,包括所有电影和歌曲,都将有一个名称。)
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
下一个片段定义了 MediaItem
的两个子类。第一个子类 Movie
封装了关于电影或影片的额外信息。它在基类 MediaItem
上添加了一个 director
属性,并相应地添加了一个初始化器。第二个子类 Song
在基类上添加了一个 artist
属性和初始化器:
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 的类型检查器能够推断出 Movie
和 Song
有一个共同的超类 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]
存储在 library
中的项目仍然在幕后是 Movie
和 Song
实例。然而,如果你遍历此数组的内容,你收到的项目会被类型化为 MediaItem
,而不是 Movie
或 Song
。为了以它们的原生类型进行操作,你需要检查它们的类型,或者将它们转换为不同的类型,如下面所述。
检查类型
使用类型检查运算符(is
)来检查一个实例是否为某个子类类型。类型检查运算符如果实例是该子类类型则返回 true
,否则返回 false
。
以下示例定义了两个变量 movieCount
和 songCount
,它们分别计算 Movie
和 Song
在 library
数组中的数量:
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
。
item is Movie
如果当前 MediaItem
是 Movie
实例,则返回 true
,否则返回 false
。同样,item is Song
检查项是否是 Song
实例。在 for-in
循环结束时,movieCount
和 songCount
的值包含每种类型的 MediaItem
实例数量。
下转型
某个类类型的常量或变量实际上可能在幕后引用的是该类的子类的一个实例。如果你认为这是这种情况,你可以尝试使用类型转换运算符(as?
或 as!
)将其转换为子类类型。
由于类型转换可能会失败,类型转换运算符有两种不同的形式。条件形式,as?
,返回你尝试转换为目标类型的可选值。强制形式,as!
,尝试类型转换并强制解包结果为一个单一的操作。
当你不确定类型转换是否会成功时,请使用类型转换运算符的条件形式(as?
)。该运算符的形式总是会返回一个可选值,如果类型转换不可行,值将为 nil
。这使你可以检查类型转换是否成功。
仅当你确定类型转换总是会成功时,请使用类型转换运算符的强制形式(as!
)。该运算符的形式如果尝试将类型转换为错误的类类型,将触发运行时错误。
以下示例遍历 MediaItem
中的每个 library
,并为每个项目打印适当的描述。为此,它需要将每个项目作为真正的 Movie
或 Song
访问,而不仅仅是作为 MediaItem
。这为了能够访问 director
或 artist
属性 Movie
或 Song
以用于描述。
在这个例子中,数组中的每个项可能是 Movie
,也可能是 Song
。你无法提前知道每个项应该使用哪个实际类,因此在循环中的每次迭代时使用类型转换操作符的条件形式(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
The example starts by trying to downcast the current item as a Movie
. Because item
is a MediaItem
instance, it’s possible that it might be a Movie
; equally, it’s also possible that it might be a Song
, or even just a base MediaItem
. Because of this uncertainty, the as?
form of the type cast operator returns an optional value when attempting to downcast to a subclass type. The result of item as? Movie
is of type Movie?
, or “optional Movie
”。
将类型为 Movie
的对象向下转型时,在库数组中的 Song
实例上执行此操作会失败。为了应对这种情况,上面的示例使用了可选绑定来检查可选的 Movie
是否包含值(即,确定向下转型是否成功)。这种可选绑定写为“if let movie = item as? Movie
”,可以读作:
“尝试将 item
视为 Movie
。如果此操作成功,将创建一个新的临时常量 movie
,并将其设置为返回的可选 Movie
中存储的值。”
如果向下转型成功,将使用 movie
的属性来打印该 Movie
实例的描述,包括其 director
的名称。类似的原则用于检查 Song
实例,并在找到 Song
时打印适当的描述(包括 artist
的名称)。
注意
转型实际上并不会修改实例或改变其值。底层实例保持不变;它只是被视为并作为转型后的类型进行处理和访问。
任意类型和 AnyObject 的类型转换
Swift 提供了两种特殊类型用于处理非特定类型:
Any
可以表示任何类型的实例,包括函数类型。AnyObject
可以表示任何类类型。
只有在明确需要它们提供的行为和功能时,才使用 Any
和 AnyObject
。总是尽量具体地指定代码中预期使用的类型。
以下是一个使用 Any
处理不同类型混合的例子,包括函数类型和非类类型。该示例创建了一个名为 things
的数组,可以存储类型为 Any
的值:
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)
的元组,电影“Ghostbusters”,以及一个接受一个 String
值并返回另一个 String
值的闭包表达式。
要发现已知仅为 Any
或 AnyObject
类型的常量或变量的具体类型,可以使用 is
或 as
模式在 switch
语句的 cases 中。下面的示例遍历 things
数组中的项,并使用 switch
语句查询每个项的类型。switch
语句的多个 cases 将匹配的值绑定到指定类型的常量,以便可以打印其值:
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
注意
TheAny
类型表示任何类型的值,包括可选类型。如果你在一个预期类型为Any
的地方使用了一个可选值,Swift 会给你一个警告。如果你确实需要将一个可选值作为Any
值使用,你可以使用as
运算符显式地将可选值转换为Any
,如下所示。
let optionalNumber: Int? = 3
things.append(optionalNumber) // Warning
things.append(optionalNumber as Any) // No warning