类型转换(Type Casting)

491 阅读8分钟

原文

type casting是检查实例类型的一种方式,或者把实例当做一个不同的他自己类层级中其他地方中的父类或者子类。

swift中的类型转换使用is和as操作符。这两个操作符提供一个简单的并且表达清晰的方式来检查一个值的类型或者把一个值转换为其他类型。

也可以使用类型转换来检车一个类型是否遵循一个协议,像Checking for Protocol Conformance中描述的。

为类型转换定义一个类层级(Defining a Class Hierarchy for Type Casting)

可以对一个类的层级来使用类型转换也可以对子类使用来检查一个指定类实例的类型也可以把那个实例转换为另一个相同层级中的类。下面三个代码块定义了一个类的层级和一个包含这些类的实例的数组,为了在类型转换的例子中使用。

第一个块定义了一个名为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]leixing :

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。为了使用他们本来的类型,需要检查他们的类型,或者把他们向下转换成不同的类型,像下面描述的。

检查类型(Checking Type)

使用类型检查(is)来检查一个实例是否是一个确定子类的类型。如果实例时子类的类型类型检查操作符返回true,如果不是的话返回flase。

下面的例子定义了两个变量,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")
// Prints "Media library contains 2 movies and 3 songs"

这个例子遍历了library数组中全部的对象。每次执行过,for-in循环吧item常量设置为数组中的下一个MediaItem。

如果当前MediaItem是一个Movie实例item is Movie返回一个true,如果不是的话返回false。相似的,item is Song检查对象是否是一个Song实例。在for-in循环最后,movieCount和songCount的值包含一个每个类型发现有多少MediaItem实例的数字。

向下转化(Downcasting)

一个准确类类型的常量后者变量可能实际上背后指向一个子类的实例。相信是这种情况的时候,可以使用一个类型转换操作符(as?或者as!)来尝试向下转换为子类类型。

因为向下转换可能失败,类型转换操作符用了两种不同的形式。条件的形式,as?,返回一个你尝试向下转换的类型的可选类型值。强制的形式,as!,有一个单一的组合动作尝试向下转换并且强制解包结果。

当你不确定向下转换是否成功的时候使用类型转化操作符的条件形式。这个形式的操作符通常返回一个可选的值,如果向下转化是不可能的话值会是nil。这使你可以给一个成功向下转换做检查。

只有当你确定向下转化总是成功的时候才使用强制形式的类型转换操作符(as!)。如果你尝试向下转换为一个不正确的类类型这个操作符的形式会触发一个运行时错误。

下面的例子遍历library中的每一个MediaItem,并且为每一个对象打印合适的描述。为了做到这个,需要把每个对象当成真是的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

例子中开始时尝试把当前的对象向下转换为一个Movie。因为对象是一个MediaItem实例,他可能是一个Movie;同样的,也可能它是一个Song,或者只是一个基础的MediaItem。因为这个不确定性,当尝试向下转化为一个子类类型的时候as?形式的类型转换符返回一个可选的值。item as? movie的返回是Movie?类型,或者“optional Movie”。

当对library数组中的song实例使用时,向下转换为Movie失败。要处理这个,上面的例子使用可选绑定来检查可选类型Movie实际上是否包含一个值(也就是,查看向下转化是否成功了)。这个可选绑定写作“if let Movie = item as ? Movie”,可以读作:

“尝试按Movie访问对象,如果成功了,把一个新的名为movie的临时常量设置为存储在放回的可选Movie中的值”。

如果向下转化成功了,movie属性会为那个Movie实例用来打印一个描述,包括他的director的名字。相似的原则也用于检查Song实例,当在library中发现Song的时候打印一个合适的描述(包括artist名字)。

转换实际上不改变实例或者改变他的值。后面的实例保持一样;只是作为它转换的类型的实例来对待和访问。

Any和AnyObject的类型转换(Type Casting for Any and AnyObject)

swift为使用非指定的类型提供了两个特殊的类型:

  • Any可以表示全部任何的类型的实例,包括函数类型
  • AnyObject可以表示任何类类型的实例。

只有在明确需要他们提供的表现和特性的时候使用Any和AnyObject。在你的代码中指定你期望使用的类型会更好。

这里是使用Any来和不同类型的混合物工作的例子,包括函数类型和非类类型。例子创建了一个名为thingsde数组,可以存储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”movie,和一个表示接受一个String值并返回另一个String值的闭包。

要查找一个只知道是Any或者AnyObject类型的常量或者变量的明确类型,你可以在switch语句的case中使用一个is或者as模型。下面的例子遍历了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

Any类型表示any类型的值,包括可选类型。如果在期望Any类型的值的地方使用一个可选类型,swift会给你一个警告。如果你确实需要作为Any值使用可选类型的值,你可以使用as操作符来明确的把可选类型转换为Any,像下面展示的一样。                                                             

let optionalNumber: Int? = 3
things.append(optionalNumber)        // Warning
things.append(optionalNumber as Any) // No warning