Swift类型转换

时间:2023-12-29 08:47:20

关于「类型转换」(Type Casting),《The Swift Programming Language》描述如下:

Type casting is a way to check the type of an instance, and/or to treat that instance as if it is a different superclass or subclass from somewhere else in its own class hierarchy.

在Swift中,有两个关键字与类型转换相关:isas。前者体现的是Swift的内省机制(introspection),用于检查某个实例的类型(类似于OC中的isKindOfClass:isMemberOfClass:);后者用于类型转换,即将某个类型实例转换为其父类的实例或者子类的实例。

Swift中的类型转换是一个非常容易理解的概念,本文将以代码为辅助对之进行简单阐述。

首先定义几个类为后文服务:

// 「媒体」类
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
} // 「电影」类
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)
}
}

然后再创建一个数组实例:

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的类型是[MediaItem]。站在物理的角度,library中存储的元素要么是Movie类型实例,要么是Song类型实例。但站在逻辑的角度,你若遍历library,则取出的实例会是MediaItem类型的,而不是Movie或者Song。所以,若想让library中的元素基于它们本来的类型处理事情,那么需要检查它们的类型或者向下转换它们的类型到其他类型。

Checking Type

用类型检查操作符is来检查一个实例是否属于特定类型。若实例属于那个类型,类型检查操作符返回true,否则返回false。

如下定义了两个变量,movieCount和songCount,分别用来计算library中Movie和Song类型实例的数量,如下:

var movieCount =
var songCount = 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"

Downcasting

「Downcasting」常常被翻译为「向下转型」。某个常量或变量在逻辑层面可能属于某种类型,但在物理层面可能属于其某个子类类型。上述library中的元素就是这种情况(逻辑层面包含的元素都属于MediaItem类型,但是实际上有的属于Movie类型,有的属于Song类型)。对于这种情况,你可以使用转换操作符as向下转型(譬如MediaItem转Song或Movie)。

在OC中,类型转换非常简单,譬如:

UIViewController *VC = [[UIViewController alloc] init];
UITableViewController *tableVC = (UITableViewController *)VC;
tableVC.tableView.background = [UIColor whiteColor];

这段代码当然会通过编译,但是会产生runtime错误,至于原因就不啰嗦了。

对于Swift也一样,downcasting也可能会失败。不同的是,在OC中,类型转换成功或失败是无法获知的(因为人家是动态语言嘛,类型检测在runtime才会进行);Swift是强类型语言,类型转换成功与否是可以检查到的,简单来说,Swift的downcasting会返回一个optional,若失败了,则该optional的值为nil

类型转换操作符有两种不同形式:

  • as?,返回一个optional,若转换成功,则包含你期待的类型实例,若失败,则是nil;
  • as!,相当于转换后强制解包,若转换失败,则会造成runtime错误;

显然,当你不确定是否能转换是否成功时,使用as?;否则,使用as!
P.S:能不能直接使用as?No!

下面的示例遍历了上文的library常量,并打印出其中所有元素的描述信息(名字,音乐作者或者电影导演),如下:

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
*/

Any and AnyObject

虽然Swift是强类型语言,但是OC不是啊,譬如OC中数组可以存放允许类型的类对象(不要求类型一致);考虑到兼容性,Swift为不确定类型提供了两种特殊类型:

  • AnyObject可以代表任何类类型的实例;
  • Any可以表示任何类型实例,包括方法/函数类型;

值得一提的是,AnyObjectAny尽可能少用,毕竟类型越清晰越好,Swift文档是这么描述的:

Use Any and AnyObject only when you explicitly need the behavior and capabilities they provide. It is always better to be specific about the types you expect to work with in your code.

AnyObject

使用Cocoa APIs时,常常会接收到一个[AnyObject]类型的数组,或者说「一个任何对象类型的数组」。这是因为OC没有明确的类型化数组。然而,很多时候我们常常可以确定知道包含在数组(获取其他集合)中元素的确切类型;在这种情况下,可以使用as!来向下对元素类型进行转换(转到比AnyObject更确切的类型)。如下是对AnyObject向下转型的一个栗子:

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!向下转型并解包到Movie类型:

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
*/

还有一种更简洁的写法:

for movie in someObjects as! [Movie] {
println("Movie: '\(movie.name)', dir. \(movie.director)")
}

Any

AnyAnyObject差不多,只是后者仅限于类类型,前者通吃所有类型。比较简单,这里不赘述了。