类型转换是一种检查实例类型的方法,或者将该实例视为来自其自身类层次结构中的其他地方的不同超类或子类。
Swift中的类型转换使用is和as运算符实现。 这两个操作符提供了一种简单和表达方式来检查值的类型或将值转换为不同类型。
您还可以使用类型转换来检查类型是否符合协议,如检查协议一致性中所述。
定义类型转换的类层次结构
- 您可以使用具有类和子类的层次结构的类型转换来检查特定类实例的类型,并将该实例转换到同一层次结构中的另一个类。 下面的三个代码片段定义了类的层次结构和包含这些类的实例的数组,用于类型转换的示例。
- 第一个片段定义了一个名为MediaItem的新基类。 此类为数组library中出现的任何类型的项提供基本类型。 具体来说,它声明了一个String类型的name属性,以及一个初始化名称初始化程序。 (假设所有元素,包括所有movie和song,都将有一个名称。)
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的公共超类,因此它推断出一个类型为[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")
]
- 存储在library中的元素仍然是Movie和Song的实例。 但是,如果迭代此数组的内容,则您接收到的项目将键入为MediaItem,而不是Movie或Song。 为了使用它们是它们自身的类型,你需要检查它们的类型,或者将它们降级到不同的类型,如下所述。
类型校检
- 使用类型检查运算符(is)来检查实例是否是某个子类型。 如果实例是该子类型,则类型检查运算符返回true,否则返回false。
- 下面的示例定义了两个变量movieCount和songCount,它们计算库数组中Movie和Song实例的数量:
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {//Item是Movie的子类
movieCount += 1
} else if item is Song {//item是Song的子类
songCount += 1
}
}
print("Media library contains \(movieCount) movies and \(songCount) songs")
// Prints "Media library contains 2 movies and 3 songs"
下转
- 某个类类型的常量或变量实际上可能指的是子类的实例。 在这种情况下,你可以尝试使用类型转换运算符(as?或as!)向下转换到子类类型。
- 因为向下转换可能失败,所以类型转换操作符有两种不同的形式。 条件形式as ?,返回您尝试向下转换的类型的可选值。 强制形式,as!,尝试向下转换并将结果解包为单个复合操作。
- 当您不确定向下转换是否将成功时,使用类型转换运算符的条件形式(as?)。 此形式的运算符将始终返回一个可选值,如果不可能进行向下转换,则该值将为nil。 这使您能检查一个类型的下转是否成功。
- 只有当你确信类型下转绝对成功时,才使用强制转换类型转换操作符(as!)。 如果您尝试向下转换为不正确的类类型,则此形式的运算符将触发运行时错误。
- 下面的示例代码中遍历library数组中的每个MediaItem,并为每个项目打印一个适当的描述。 为此,它需要访问每个item的真实类型是Movie还是Song,而不只是MediaItem。 这是必要的,以便它能够访问Movie或Song的director或artist属性,以便在说明中使用。
- 在此示例中,数组中的每个项目都可能是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
- 该示例首先尝试将当前项目作为电影进行向下转换。 因为item是一个MediaItem实例,它可能是一个Movie类型; 同样,它也可能是一Song类型,或者甚至只是一个基本MediaItem类型。 由于这种不确定性, 形式的类型转换操作符在尝试向下转换为子类类型时返回可选值。 item的转换结果可能是“Movie”也可能是“Optional Movie”。
- 当library数组中的类型是Song的时候,下转到Movie类型失败。 为了解决这个问题,上面的例子使用可选的绑定来检查可选的Movie是否实际上包含一个值(也就是说,判断下转是否成功。)这个可选的绑定是写为“if let movie = item as? Movie“,可以读作:“尝试以Movie访问item。 如果这是成功的,请将一个名为movie的新临时常量设置为存储在返回的可选Movie中的值。
- 如果向下转换成功,则movie的属性用于打印该Movie实例的描述,包括其director的名称。 类似的原理用于检查Song实例,并在library中找到Song打印适当的描述(包括artist姓名)。
***注意****:转换不会实际修改实例或更改其值。 底层实例保持不变; 它被简单地创建并作为它已经被转换到的类型的实例被访问。
对Any和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类型的常量或变量的特定类型,可以在switch语句的case中使用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类型的值。 如果真的需要使用可选值作为Any值,可以使用as运算符将可选值显式转换为Any,如下所示。
let optionalNumber: Int? = 3
things.append(optionalNumber) // Warning
things.append(optionalNumber as Any) // No warning