Swift编程十九(类型转换)
案例代码下载
类型转换
类型转换是一种检查实例类型的方法,或者将该实例视为与其自己的类层次结构中的其他位置不同的超类或子类。
Swift中的类型转换是使用is和as运算符实现的。这两个运算符提供了一种简单而富有表现力的方法来检查值的类型或将值转换为其他类型。
还可以使用类型转换来检查类型是否符合协议,如检查协议一致性中所述。
为类型转换定义类层次结构
可以使用类型转换来检查特定类实例在类和子类的层次结构中的类型,并将该实例转换为同一层次结构中的另一个类。下面的三个代码片段定义了类的层次结构和包含这些类的实例的数组,用于类型转换的示例。
第一个片段定义了一个名为MediaItem的新基类。此类为数字媒体库中显示的任何类型的项目提供基本功能。具体来说,它声明了一个String类型的属性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")
]
用两个Movie实例和三个Song实例创建library常量数组。但是,如果迭代此数组的内容,则收到的项目将被键入MediaItem,而不是Movie或Song。要将它们作为本类型使用,需要检查其类型,或将它们向下转换为其他类型,如下所述。
检查类型
使用类型检查运算符(is)来检查实例是否属于某个子类类型。如果实例属于该子类类型,则类型检查操作符返回true,如果不是,则返回false。
下面的例子定义两个变量,movieCount和songCount,其计数library数组中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")
此示例遍历library数组中的所有项。每次通过for-in循环将item常量设置为MediaItem数组中的下一个。
如果当前MediaItem是Movie实例返回返回ture,如果不是,则返回false。同样,检查item是否为Song实例。在for-in循环结束时,movieCount和songCount值表示含有各自类型计数的多少。
类型转换
某个类类型的常量或变量实际上可能是子类的实例。在这种情况下,可以尝试使用类型转换运算符(as?或as!)向下转换为子类类型。
由于向下转换可能会失败,因此类型转换运算符有两种不同的形式。条件形式as?,返回您尝试向下转换的类型的可选值。强制形式as!尝试向下转换并强制转换结果。
当不确定向下转换是否成功时,请使用类型转换运算符(as?)的条件形式。这种形式的运算符将始终返回一个可选值,如果无法进行向下转换,则该值将为nil。这需要坚持转换是否成功。
仅当确定向下转换将始终成功时,才使用类型转换运算符(as!)的强制形式。如果尝试向下转换为不正确的类类型,则此形式的运算符将触发运行时错误。
下面的例子在library中迭代每个MediaItem,并打印每个项目的相应说明。要做到这一点,它需要将每个项目作为一个真实Movie或Song,而不仅仅作为一个MediaItem。这是为了能够访问Movie的director属性或Song的artist属性在说明中使用。
在此示例中,数组中的每个项可能是Movie,或者可能是Song。事先并不知道每个项目使用哪个实际类,因此使用类型转换(as?)的条件形式来检查每次循环时的向下转换为合适的operator:
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)")
}
}
该示例首先尝试将当前item向下转换为Movie。因为item是一个MediaItem例子,这是可能的,它可能是一个Movie; 同样,它也可能是一个Song,甚至只是一个MediaItem。由于这种不确定性,类型转换运算符的形式as?在尝试向下转换为子类类型时返回一个可选值。item as? Movie结果是Movie?类型或“optional Movie”。
数组中的Movie实例转换为Song实例时,向下转换失败。为了解决这个问题,上面的示例使用可选绑定来检查可选Movie实际上是否包含一个值(即,类型转换是否成功。)此可选绑定写为“if let movie = item as? Movie”,可以读作:
“尝试转换item为Movie。如果成功,则设置一个新的临时常量存储在返回的可选Movie中。”
如果向下转换成功,movie则使用属性来打印该Movie实例的描述,包括其名称和导演。类似的原则用于检查Song实例,并在数组中找到Song时打印适当的描述(包括名称和表演者)。
注意: 转换实际上不会修改实例或更改其值。实例保持不变; 它被简单地处理和访问为它的类型的实例。
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类型转换为特定类型的常量或变量,可以使用一个is或as模式作为switch语句的情况。下面的示例遍历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")
}
}
注意: Any类型表示任何类型的值,包括可选类型。如果使用期望值为可选类型,Swift会向发出警告Any。如果确实需要使用可选值作为Any值,则可以使用as运算符显式地将可选值转换为Any,如下所示。
let optionalNumber: Int? = 3
things.append(optionalNumber)// 有警告
things.append(optionalNumber as Any)// 没有警告