类型转换是一种用来检查类型的实例,或着把这个实例看作一个不同与它自己类层级中的一个父类或子类。Type casting is a way to check the type of an instance, or to treat that instance as a different superclass or subclass from somewhere else in its own class hierarchy.
类型转换在swift里面用is
和as
运算符来表示。这两个运算符提供的是一个最简单明了的方法来检查某个值的类型或着把这个值转换成不同的类型。我们也可以使用类型转换来检查某个类型是否实现某个协议,详情见协议章节。
我们可以使用类型转换在类的层级或子类中用来检查某个实例的类型和将该实例转换到相同的类层级中的另一个类里面去。
这个第一段代码定义的是一个基类MediaItem
,提供的任何物品都能出现在这个数字媒体库里面的一个功能。定义了一个类型为String
的name
的属性和一个init name
的构造器。(假设所有的物品,可以分为电影,歌曲… 都会有一个名字的)
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
第二段代码定义的是MediaItem
的两个子类。第一个子类Movie,封装了有关Movie的一些附加信息。包括在基类MediaItem用相对应的构造器添加了director属性,第二个子类也是同样的道理,为积累添加了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的实例。这个数组是通过构造器里面数组的值推断出来的类型,swift里面的类型检测器能够推断出Movie和Song有个相同的父类MediaItem,所以这两个子类将[MediaItem]
推断出成这个library的数组。
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
,这两个变量用来计算library数组里面Movie和Song实例的数量。
var movieCount = 0
var songCount = 0
for item in library {
// 这个类型检查操作 值用来找这个数组中Movie和Song的数量
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}
print("Media library contains \(movieCount) movies and \(songCount) songs")
// 输出:Media library contains 2 movies and 3 songs
这个例子通过library
里面的所有物品进行迭代,每一次迭代,for-in
循环在数组中设置的item
常量一直到MediaItem
,所以说在数组中设置的这个常量,最后用条件语句和类型检查if运算符在这个类里面寻找movieCount属性以每次增加一的方式来确定该类中所有这个属性的数量。
item is Movie
返回的是true如果说当前的MediaItem中是有Movie实例的,item is Song
也是同样的道理,在for-in循环的最后movieCount和songCount包含的这个值就是每一个类型中有多少个MediaItem的实例。
在幕后某个类的变量或常量有可能属于一个子类的实例,如果是这种情况我们可以用类型装欢运算符as?或as!
来向下转换这个子类的类型。因为类型向下转换可能失败,所以这个类型转换运算符有两种不同的形式,- 条件形式as?
返回一个我们试图向下装欢的可选类型,- 强制形式as!
尝试向下转换并且强制展开结果的一个复合操作。
使用条件形式的类型转换运算符as?
只有当我们不确定这个向下转换是否成功的时候,这种形式的转换总是返回一个可选类型的值,如果说这个转换失败了则说明这个返回的值是nil
。这种形式的向下转换允许我们用来检查这个转换是否成功。
使用强制形式的类型转换运算符as!
只有当我们确定这个向下转换一定会成功的情况下。这种行是的运算符将会触发一次运行错误runtime error
如果我们向下转换了一个错误的类类型(class type)。
下面的这个例子是在library数组里面迭代mediaItem中的物品,并且输出每个物品和相对应的表达。要想这个做,我们就的读取每一个物品做为一个true的Movie
或着Song
的实例,而并不是MediaItem
(这是一个类),因为呢我们要读取的是它的属性值Movie和Song中的director和artist属性和该属性相对应的表达描述。
下面这个例子里面数组中的item可能是Movie或者Song,所以我们事前并不知道这个item属于哪一个类,这个时候我们要通过整个循环用类型转换运算符的条件形式来检查这个向下转换。因为item
是mediaItem
的一个实例,同样的item有可能是Movie或者是Song,因为这些不确定性,这个类型转换运算符返回的是一个可选值,当我们尝试向下转换到一个子类类型的时候,item as? Movie
的返回结果只会是Movie?
或者可选的Movie。
在library数组里面应用Song的实例向下转换为Movie失败了,为了解决这个问题,上面这个例子使用了可选绑定来检查这个可选的Movie是否包含一个值,(也就是用来找出这个向下转换是否成功),这个可选绑定 if let movie = item as? Movie
可以读作 try to access item as a Movie,if this is successful,set a new temporary constant called movie to the value stored in the returned optional Movie。
// library中的item 是一个描述Song或着Movie的实例
for item in library {
/* if let 可选绑定的关键字,movie新的常量名,
item as? Movie 在数组里把Movie作为item来读取
把读取到的值(item as? Movie)作为一个绑定在常量movie中 */
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
如果说这个向下转换成功了,然后这个movie的属性将连同描述一起作为Movie的实例输出。包括director的名字,同样的道理,song的属性也会进行如此的输出。
Swift为不确定的类型提供了两种特殊的类型别名,Swift provides two special types for working with nonspecific types
使用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)" })
这个数组包含了 两个Int
类型的值,两个Double
类型的值,一个String
值,一个类型为(Double, Double)
的元组。一个名为Ghostbusters的Movie,和一个输入值为String和返回值为String的闭包表达。
我们可以在switch语句中用is
或as
运算符开区分并找出只知道是Any
或者AnyObject
的类型的具体类型。下面的这个例子就是在things
数组中迭代每一个物品thing
。用switch语句来查询每一个物品thing
的具体类型,有些switch语句的成员case用来绑定匹配到的值给一个明确类型的常量,并且打印出这个值用来进一步找出这个不知名类的具体类型。
下面这个例子中使用了 很多语句 比如 for-in,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