造型
造型是检查一个实例的类型,并且/或者将这个实例作为它所处类继承谱系中的超类或者子类处理 的一种方式。
Swift的造型通过is和as操作符实现。这两个操作符提供了一种简单而且方便的方式检查一个值的类型或者将一个值造型为另外一种类型。
也可以使用操作来检查一个类型是否遵循了一个协议,就像 Checking for Protocol Conformance 一节的描述。
为造型定义一个类的谱系
可以对一个类谱系中的类或者子类采用造型来检查一个特定类实例的类型或者将特定类型的实例造型为谱系中的另外一个类。下面的三个代码片段定义了一个类的谱系和一个包含这些类实例的数组,用作造型的示例。
第一个代码片段定义了一个新的基本类叫做MediaItem。这个类提供了在数字媒体图书馆中存在的任何种类藏品的基本功能。它定义了一个String类型的name属性和一个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
} else if item is Song {
++songCount
}
}
println("Media library contains \(movieCount) movies and \(songCount) songs")
// prints "Media library contains 2 movies and 3 songs"
这个例子中遍历了所有library数组中的内容。每次for-in循环中,会将下数组中的下一个MediaItem赋值给常量item。
如果当前的MediaItem是一个Movie实例,那么item is Movie 返回true,反之返回false。类似的,item is Song 检查当前内容是否是一个Song实例。在for-in循环的末尾,movieCount和songCount包含了每种类型内容在数组中被发现的个数。
向下造型
在后台,一个特定类的常量或者变量可能指向一个这个类子类。如果是这种情况,可以通过造型操作符(type case operator)(as?或者as!)将其造型为子类。
因为向下造型可能会失败,造型操作符有两种不同的方式。可选方式,as?,返回一个可选的想要向下造型的类型。强制方式,as!,尝试将向下造型而且强制拆包结果作为一个单一的复合操作。
当不能确信向下造型一定会成功时,使用可选方式的造型操作符(as?)。可选模式始终会返回一个可选值,如果造型失败返回值将是nil。这样可以检查造型是否成功。
只有当可以确信向下造型一定会成功时,使用强制方式的造型操作符(as!)。当向下造型的目标类型不对时,这种方式会触发一个运行时错误。
下面的例子遍历了library数组中的每个MediaItem,并且将每个内容中的对应描述打印了出来。为了做到这点,需要对每个内容作为Movie或者Song进行访问,而不是作为MeidaItem。内容的描述中用到了它们特有的属性,所以必须要能够访问Movie的director属性或者Song的artist属性。
这个例子中,数组中的每一项,可能是Movie,也可能是Song。你不知道每一项的更多信息,使用可选方式的造型操作符(as?)在每次循环中检查向下造型的做法是合适的:
for item in library {
if let movie = item as? Movie {
println("Movie: '\(movie.name)', dir. \(movie.director)")
} else if let song = item as? Song {
println("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?或者说是“可选的Movie”。
当碰到liberary书中的一个Song实例时,向下造型为Movie会失败。为了解决这个问题,上面的例子使用了可选绑定来检查可选Movie实际包含的值(其实就是检查向下造型是否成功。)这个可选绑定写作“if let movie = item as? Movie”它的意思是:
“试着将item作为Movie访问。如果成功,用返回的可选Movie中的值给临时常量moive赋值。”
如果造型成功,movie中的属性被用来打印那个Movie实例的描述,其中包括了它的director的名字。当一个Song被找到,同样的规则在检查Song实例时也被使用,也会打印出对应的描述(包括artist名字)。
NOTE
造型实际上没有改变实例也没有改变实例的值。实例仍然是实例,只不过被当作造型的目标类型处理和访问了而已。
对Any和AnyObject造型
Swift提供了两个特殊的类型别称表示没有指定的类型:
AnyObject可以表示任意类的实例。
Any竟然可以表示任意实例,包括函数类型。
NOTE
属实需要使用Any和AnyObject的行为或者功能时才可以使用它们。通常,在代码中指定类型比较好。
任何对象(AnyObject)
在使用Cocoa API的时候,经常会接收到类型是[AnyObject]的数组,或者叫做“一个值是任意对象类型的数组”。这是因为OC语言没有明确类型的数组。但是,经常根据API提供的信息可以确信那个数组中含有某些特定的对象。
这种情况下,可以使用强制版本的造型操作符(as)对数组中的每一项都造型为特定的类型而不再使用AnyObject,这样做不需要可选类型解包。
下面的例子定义了一个[AnyObject]类型的数组,用Movie的三个实例填充了它:
let someObjects: [AnyObject] = [
Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
Movie(name: "Moon", director: "Duncan Jones"),
Movie(name: "Alien", director: "Ridley Scott")
]
因为知道这个数组中之包含Movie实例,可以用强制版本的造型(as)操作符对其直接进行造型和拆包:
for object in someObjects {
let movie = object as Movie
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
// Movie: 'Moon', dir. Duncan Jones
// Movie: 'Alien', dir. Ridley Scott
这个循环的更简洁版本是,将someObjects 数组造型为[Movie]类型而不是对每个内容进行向下造型:
for movie in someObjects as [Movie] {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}
// Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
// Movie: 'Moon', dir. Duncan Jones
// Movie: 'Alien', dir. Ridley Scott
任何(Any)
这里的例子使用了Any来处理一个不同类型的混合体,其中包括了函数类型和不是类的类型。这个例子创建了一个数组叫做things,它其中存储了Any类型的值:
ar 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)" })
tings数组包含两个Int值、两个Double值、一个String值,一个{Double,Double}类型的元组、电影“Ghostbusters”还有一个带一个String值参数返回另一个String值的臂包表达式。
可以在switch的case语句中使用is和as操作符,从仅仅已知为Any或者AnyObject类型的常量或者变量中发现特定的类型。下面的例子遍历了things数组的每一项,同时用一个switch语句查询每一项的类型。若干个switch的case语句将匹配到的值绑定到特定类型的常量,进而可以将值打印出来:
for thing in things {
switch thing {
case 0 as Int:
println("zero as an Int")
case 0 as Double:
println("zero as a Double")
case let someInt as Int:
println("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
println("a positive double value of \(someDouble)")
case is Double:
println("some other double value that I don't want to print")
case let someString as String:
println("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
println("an (x, y) point at \(x), \(y)")
case let movie as Movie:
println("a movie called '\(movie.name)', dir. \(movie.director)")
case let stringConverter as String -> String:
println(stringConverter("Michael"))
default:
println("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
NOTE
switch语句中的case使用了强制版本的造型操作符(as,而不是as?)来检查和造型特定的类型。在switch 的case语句上下文中,这样的检查始终是安全的。