Swift5.1—类型转换

2020-07-09  本文已影响0人  anny_4243

类型转换可以判断实例的类型,也可以将实例看做是其父类或者子类的实例。

类型转换在 Swift 中使用 isas 操作符实现。这两个操作符分别提供了一种简单达意的方式去检查值的类型或者转换它的类型。

你也可以用它来检查一个类型是否遵循了某个协议,就像在 检验协议遵循 部分讲述的一样。

为类型转换定义类层次

你可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。下面的三个代码段定义了一个类层次和一个包含了这些类实例的数组,作为类型转换的例子。

第一个代码片段定义了一个新的基类 MediaItem。这个类为任何出现在数字媒体库的媒体项提供基础功能。特别的,它声明了一个 String 类型的 name 属性,和一个 init(name:) 初始化器。(假定所有的媒体项都有个名称。)

class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

下一个代码段定义了 MediaItem 的两个子类。第一个子类 Movie 封装了与电影相关的额外信息,在父类(或者说基类)的基础上增加了一个 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 的类型检测器能够推断出 MovieSong 有共同的父类 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")
]
// 数组 library 的类型被推断为 [MediaItem]

在幕后 library 里存储的媒体项依然是 MovieSong 类型的。但是,若你迭代它,依次取出的实例会是 MediaItem 类型的,而不是 MovieSong 类型。为了让它们作为原本的类型工作,你需要检查它们的类型或者向下转换它们到其它类型,就像下面描述的一样。

检查类型

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

下面的例子定义了两个变量,movieCountsongCount,用来计算数组 libraryMovieSong 类型的实例数量:

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")
// 打印“Media library contains 2 movies and 3 songs”

示例迭代了数组 library 中的所有项。每一次,for-in 循环设置 item 常量为数组中的下一个 MediaItem 实例。

若当前 MediaItem 是一个 Movie 类型的实例,item is Movie 返回 true,否则返回 false。同样的,item is Song 检查 item 是否为 Song 类型的实例。在循环结束后,movieCountsongCount 的值就是被找到的属于各自类型的实例的数量。

向下转型

某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定是这种情况时,你可以尝试用类型转换操作符(as?as!)向下转到它的子类型。

因为向下转型可能会失败,类型转型操作符带有两种不同形式。条件形式 as? 返回一个你试图向下转成的类型的可选值。强制形式as!把试图向下转型和强制解包转换结果结合为一个操作。

当你不确定向下转型可以成功时,用类型转换的条件形式(as?)。条件形式的类型转换总是返回一个可选值,并且若下转是不可能的,可选值将是 nil。这使你能够检查向下转型是否成功。

只有你可以确定向下转型一定会成功时,才使用强制形式(as!)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。

下面的例子,迭代了 library 里的每一个 MediaItem,并打印出适当的描述。要这样做,item 需要真正作为 MovieSong 的类型来使用,而不仅仅是作为 MediaItem。为了能够在描述中使用 MovieSongdirectorartist 属性,这是必要的。

在这个示例中,数组中的每一个 item 可能是 MovieSong。事前你不知道每个 item 的真实类型,所以这里使用条件形式的类型转换(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

示例首先试图将 item 下转为 Movie。因为 item 是一个 MediaItem 类型的实例,它可能是一个 Movie;同样,它也可能是一个 Song,或者仅仅是基类 MediaItem。因为不确定,as? 形式在试图下转时将返回一个可选值。item as? Movie 的返回值是 Movie? 或者说“可选 Movie”。

当向下转型为 Movie 应用在两个 Song 实例时将会失败。为了处理这种情况,上面的例子使用了可选绑定(optional binding)来检查可选 Movie 真的包含一个值(这个是为了判断下转是否成功。)可选绑定是这样写的“if let movie = item as? Movie”,可以这样解读:

“尝试将 item 转为 Movie 类型。若成功,设置一个新的临时常量 movie 来存储返回的可选 Movie 中的值”

若向下转型成功,然后 movie 的属性将用于打印一个 Movie 实例的描述,包括它的导演的名字 director。相似的原理被用来检测 Song 实例,当 Song 被找到时则打印它的描述(包含 artist 的名字)。

注意

转换没有真的改变实例或它的值。根本的实例保持不变;只是简单地把它作为它被转换成的类型来使用。

上一篇 下一篇

猜你喜欢

热点阅读