這里的教程為Swift官方教程中文版。

類型轉(zhuǎn)換

類型轉(zhuǎn)換可以判斷實(shí)例的類型,也可以將實(shí)例看做是其父類或者子類的實(shí)例。

類型轉(zhuǎn)換在 Swift 中使用 isas 操作符實(shí)現(xiàn)。這兩個操作符分別提供了一種簡單達(dá)意的方式去檢查值的類型或者轉(zhuǎn)換它的類型。

你也可以用它來檢查一個類型是否遵循了某個協(xié)議,就像在 檢驗(yàn)協(xié)議遵循 部分講述的一樣。

為類型轉(zhuǎn)換定義類層次

你可以將類型轉(zhuǎn)換用在類和子類的層次結(jié)構(gòu)上,檢查特定類實(shí)例的類型并且轉(zhuǎn)換這個類實(shí)例的類型成為這個層次結(jié)構(gòu)中的其他類型。下面的三個代碼段定義了一個類層次和一個包含了這些類實(shí)例的數(shù)組,作為類型轉(zhuǎn)換的例子。

第一個代碼片段定義了一個新的基類 MediaItem。這個類為任何出現(xiàn)在數(shù)字媒體庫的媒體項(xiàng)提供基礎(chǔ)功能。特別的,它聲明了一個 String 類型的 name 屬性,和一個 init(name:) 初始化器。(假定所有的媒體項(xiàng)都有個名稱。)

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

下一個代碼段定義了 MediaItem 的兩個子類。第一個子類 Movie 封裝了與電影相關(guān)的額外信息,在父類(或者說基類)的基礎(chǔ)上增加了一個 director(導(dǎo)演)屬性,和相應(yīng)的初始化器。第二個子類 Song,在父類的基礎(chǔ)上增加了一個 artist(藝術(shù)家)屬性,和相應(yīng)的初始化器:

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)
    }
}

最后一個代碼段創(chuàng)建了一個數(shù)組常量 library,包含兩個 Movie 實(shí)例和三個 Song 實(shí)例。library 的類型是在它被初始化時(shí)根據(jù)它數(shù)組中所包含的內(nèi)容推斷來的。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")
]
// 數(shù)組 library 的類型被推斷為 [MediaItem]

在幕后 library 里存儲的媒體項(xiàng)依然是 MovieSong 類型的。但是,若你迭代它,依次取出的實(shí)例會是 MediaItem 類型的,而不是 MovieSong 類型。為了讓它們作為原本的類型工作,你需要檢查它們的類型或者向下轉(zhuǎn)換它們到其它類型,就像下面描述的一樣。

檢查類型

類型檢查操作符is)來檢查一個實(shí)例是否屬于特定子類型。若實(shí)例屬于那個子類型,類型檢查操作符返回 true,否則返回 false。

下面的例子定義了兩個變量,movieCountsongCount,用來計(jì)算數(shù)組 libraryMovieSong 類型的實(shí)例數(shù)量:

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”

示例迭代了數(shù)組 library 中的所有項(xiàng)。每一次,for-in 循環(huán)設(shè)置 item 常量為數(shù)組中的下一個 MediaItem 實(shí)例。

若當(dāng)前 MediaItem 是一個 Movie 類型的實(shí)例,item is Movie 返回 true,否則返回 false。同樣的,item is Song 檢查 item 是否為 Song 類型的實(shí)例。在循環(huán)結(jié)束后,movieCountsongCount 的值就是被找到的屬于各自類型的實(shí)例的數(shù)量。

向下轉(zhuǎn)型

某類型的一個常量或變量可能在幕后實(shí)際上屬于一個子類。當(dāng)確定是這種情況時(shí),你可以嘗試用類型轉(zhuǎn)換操作符as?as!)向下轉(zhuǎn)到它的子類型。

因?yàn)橄蛳罗D(zhuǎn)型可能會失敗,類型轉(zhuǎn)型操作符帶有兩種不同形式。條件形式 as? 返回一個你試圖向下轉(zhuǎn)成的類型的可選值。強(qiáng)制形式 as! 把試圖向下轉(zhuǎn)型和強(qiáng)制解包轉(zhuǎn)換結(jié)果結(jié)合為一個操作。

當(dāng)你不確定向下轉(zhuǎn)型可以成功時(shí),用類型轉(zhuǎn)換的條件形式(as?)。條件形式的類型轉(zhuǎn)換總是返回一個可選值,并且若下轉(zhuǎn)是不可能的,可選值將是 nil。這使你能夠檢查向下轉(zhuǎn)型是否成功。

只有你可以確定向下轉(zhuǎn)型一定會成功時(shí),才使用強(qiáng)制形式(as!)。當(dāng)你試圖向下轉(zhuǎn)型為一個不正確的類型時(shí),強(qiáng)制形式的類型轉(zhuǎn)換會觸發(fā)一個運(yùn)行時(shí)錯誤。

下面的例子,迭代了 library 里的每一個 MediaItem,并打印出適當(dāng)?shù)拿枋?。要這樣做,item 需要真正作為 MovieSong 的類型來使用,而不僅僅是作為 MediaItem。為了能夠在描述中使用 MovieSongdirectorartist 屬性,這是必要的。

在這個示例中,數(shù)組中的每一個 item 可能是 MovieSong。事前你不知道每個 item 的真實(shí)類型,所以這里使用條件形式的類型轉(zhuǎn)換(as?)去檢查循環(huán)里的每次下轉(zhuǎn):

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 下轉(zhuǎn)為 Movie。因?yàn)?item 是一個 MediaItem 類型的實(shí)例,它可能是一個 Movie;同樣,它也可能是一個 Song,或者僅僅是基類 MediaItem。因?yàn)椴淮_定,as? 形式在試圖下轉(zhuǎn)時(shí)將返回一個可選值。item as? Movie 的返回值是 Movie? 或者說“可選 Movie”。

當(dāng)向下轉(zhuǎn)型為 Movie 應(yīng)用在兩個 Song 實(shí)例時(shí)將會失敗。為了處理這種情況,上面的例子使用了可選綁定(optional binding)來檢查可選 Movie 真的包含一個值(這個是為了判斷下轉(zhuǎn)是否成功。)可選綁定是這樣寫的“if let movie = item as? Movie”,可以這樣解讀:

“嘗試將 item 轉(zhuǎn)為 Movie 類型。若成功,設(shè)置一個新的臨時(shí)常量 movie 來存儲返回的可選 Movie 中的值”

若向下轉(zhuǎn)型成功,然后 movie 的屬性將用于打印一個 Movie 實(shí)例的描述,包括它的導(dǎo)演的名字 director。相似的原理被用來檢測 Song 實(shí)例,當(dāng) Song 被找到時(shí)則打印它的描述(包含 artist 的名字)。

注意

轉(zhuǎn)換沒有真的改變實(shí)例或它的值。根本的實(shí)例保持不變;只是簡單地把它作為它被轉(zhuǎn)換成的類型來使用。

AnyAnyObject 的類型轉(zhuǎn)換

Swift 為不確定類型提供了兩種特殊的類型別名:

  • Any 可以表示任何類型,包括函數(shù)類型。
  • AnyObject 可以表示任何類類型的實(shí)例。

只有當(dāng)你確實(shí)需要它們的行為和功能時(shí)才使用 AnyAnyObject。最好還是在代碼中指明需要使用的類型。

這里有個示例,使用 Any 類型來和混合的不同類型一起工作,包括函數(shù)類型和非類類型。它創(chuàng)建了一個可以存儲 Any 類型的數(shù)組 things

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 數(shù)組包含兩個 Int 值,兩個 Double 值,一個 String 值,一個元組 (Double, Double),一個 Movie 實(shí)例“Ghostbusters”,以及一個接受 String 值并返回另一個 String 值的閉包表達(dá)式。

你可以在 switch 表達(dá)式的 case 中使用 isas 操作符來找出只知道是 AnyAnyObject 類型的常量或變量的具體類型。下面的示例迭代 things 數(shù)組中的每一項(xiàng),并用 switch 語句查找每一項(xiàng)的類型。有幾個 switch 語句的 case 綁定它們匹配到的值到一個指定類型的常量,從而可以打印這些值:

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 類型可以表示所有類型的值,包括可選類型。Swift 會在你用 Any 類型來表示一個可選值的時(shí)候,給你一個警告。如果你確實(shí)想使用 Any 類型來承載可選值,你可以使用 as 操作符顯式轉(zhuǎn)換為 Any,如下所示:

let optionalNumber: Int? = 3
things.append(optionalNumber)        // 警告
things.append(optionalNumber as Any) // 沒有警告
? 錯誤處理 嵌套類型 ?
?