翻譯:xiehurricane
校對:happyming

類型轉換(Type Casting)


本頁包含內容:

類型轉換是一種檢查類實例的方式,並且或者也是讓實例作為它的父類或者子類的一種方式。

類型轉換在 Swift 中使用isas操作符實現。這兩個操作符提供了一種簡單達意的方式去檢查值的類型或者轉換它的類型。

你也可以用來檢查一個類是否實現了某個協議,就像在 Checking for Protocol Conformance部分講述的一樣。

定義一個類層次作為例子

你可以將它用在類和子類的層次結構上,檢查特定類實例的類型並且轉換這個類實例的類型成為這個層次結構中的其他類型。這下面的三個代碼段定義了一個類層次和一個包含了幾個這些類實例的數組,作為類型轉換的例子。

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

  1. class MediaItem {
  2. var name: String
  3. init(name: String) {
  4. self.name = name
  5. }
  6. }

下一個代碼段定義了 MediaItem 的兩個子類。第一個子類Movie,在父類(或者說基類)的基礎上增加了一個 director(導演) 屬性,和相應的初始化器。第二個類在父類的基礎上增加了一個 artist(藝術家) 屬性,和相應的初始化器:

  1. class Movie: MediaItem {
  2. var director: String
  3. init(name: String, director: String) {
  4. self.director = director
  5. super.init(name: name)
  6. }
  7. }
  8. class Song: MediaItem {
  9. var artist: String
  10. init(name: String, artist: String) {
  11. self.artist = artist
  12. super.init(name: name)
  13. }
  14. }

最後一個代碼段創建了一個數組常量 library,包含兩個Movie實例和三個Song實例。library的類型是在它被初始化時根據它數組中所包含的內容推斷來的。Swift 的類型檢測器能夠演繹出MovieSong 有共同的父類 MediaItem ,所以它推斷出 MediaItem[] 類作為 library 的類型。

  1. let library = [
  2. Movie(name: "Casablanca", director: "Michael Curtiz"),
  3. Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
  4. Movie(name: "Citizen Kane", director: "Orson Welles"),
  5. Song(name: "The One And Only", artist: "Chesney Hawkes"),
  6. Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
  7. ]
  8. // the type of "library" is inferred to be MediaItem[]

在幕後library 裡存儲的媒體項依然是 MovieSong 類型的,但是,若你迭代它,取出的實例會是 MediaItem 類型的,而不是 MovieSong 類型的。為了讓它們作為它們本來的類型工作,你需要檢查它們的類型或者向下轉換它們的類型到其它類型,就像下面描述的一樣。

檢查類型(Checking Type)

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

下面的例子定義了兩個變量,movieCountsongCount,用來計算數組libraryMovieSong 類型的實例數量。

  1. var movieCount = 0
  2. var songCount = 0
  3. for item in library {
  4. if item is Movie {
  5. ++movieCount
  6. } else if item is Song {
  7. ++songCount
  8. }
  9. }
  10. println("Media library contains \(movieCount) movies and \(songCount) songs")
  11. // prints "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的值就是被找到屬於各自的類型的實例數量。

向下轉型(Downcasting)

某類型的一個常量或變量可能在幕後實際上屬於一個子類。你可以相信,上面就是這種情況。你可以嘗試向下轉到它的子類型,用類型轉換操作符(as)

因為向下轉型可能會失敗,類型轉型操作符帶有兩種不同形式。可選形式( optional form) as? 返回一個你試圖下轉成的類型的可選值(optional value)。強制形式 as 把試圖向下轉型和強制解包(force-unwraps)結果作為一個混合動作。

當你不確定向下轉型可以成功時,用類型轉換的可選形式(as?)。可選形式的類型轉換總是返回一個可選值(optional value),並且若下轉是不可能的,可選值將是 nil 。這使你能夠檢查向下轉型是否成功。

只有你可以確定向下轉型一定會成功時,才使用強制形式。當你試圖向下轉型為一個不正確的類型時,強制形式的類型轉換會觸發一個運行時錯誤。

下面的例子,迭代了library裡的每一個 MediaItem ,並打印出適當的描述。要這樣做,item需要真正作為MovieSong的類型來使用。不僅僅是作為 MediaItem。為了能夠使用MovieSongdirectorartist屬性,這是必要的。

在這個示例中,數組中的每一個item可能是 MovieSong。 事前你不知道每個item的真實類型,所以這裡使用可選形式的類型轉換 (as?)去檢查循環裡的每次下轉。

  1. for item in library {
  2. if let movie = item as? Movie {
  3. println("Movie: '\(movie.name)', dir. \(movie.director)")
  4. } else if let song = item as? Song {
  5. println("Song: '\(song.name)', by \(song.artist)")
  6. }
  7. }
  8. // Movie: 'Casablanca', dir. Michael Curtiz
  9. // Song: 'Blue Suede Shoes', by Elvis Presley
  10. // Movie: 'Citizen Kane', dir. Orson Welles
  11. // Song: 'The One And Only', by Chesney Hawkes
  12. // Song: 'Never Gonna Give You Up', by Rick Astley

示例首先試圖將 item 下轉為 Movie。因為 item 是一個 MediaItem 類型的實例,它可能是一個Movie;同樣,它可能是一個 Song,或者僅僅是基類 MediaItem。因為不確定,as?形式在試圖下轉時將返還一個可選值。 item as Movie 的返回值是Movie?類型或 「optional Movie」。

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

「嘗試將 item 轉為 Movie類型。若成功,設置一個新的臨時常量 movie 來存儲返回的可選Movie

若向下轉型成功,然後movie的屬性將用於打印一個Movie實例的描述,包括它的導演的名字director。當Song被找到時,一個相近的原理被用來檢測 Song 實例和打印它的描述。

注意:
轉換沒有真的改變實例或它的值。潛在的根本的實例保持不變;只是簡單地把它作為它被轉換成的類來使用。

AnyAnyObject的類型轉換

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

  • AnyObject可以代表任何class類型的實例。
  • Any可以表示任何類型,除了方法類型(function types)。

注意:
只有當你明確的需要它的行為和功能時才使用AnyAnyObject。在你的代碼裡使用你期望的明確的類型總是更好的。

AnyObject類型

當需要在工作中使用 Cocoa APIs,它一般接收一個AnyObject[]類型的數組,或者說「一個任何對像類型的數組」。這是因為 Objective-C 沒有明確的類型化數組。但是,你常常可以確定包含在僅從你知道的 API 信息提供的這樣一個數組中的對象的類型。

在這些情況下,你可以使用強制形式的類型轉換(as)來下轉在數組中的每一項到比 AnyObject 更明確的類型,不需要可選解析(optional unwrapping)。

下面的示例定義了一個 AnyObject[] 類型的數組並填入三個Movie類型的實例:

  1. let someObjects: AnyObject[] = [
  2. Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
  3. Movie(name: "Moon", director: "Duncan Jones"),
  4. Movie(name: "Alien", director: "Ridley Scott")
  5. ]

因為知道這個數組只包含 Movie 實例,你可以直接用(as)下轉並解包到不可選的Movie類型(ps:其實就是我們常用的正常類型,這裡是為了和可選類型相對比)。

  1. for object in someObjects {
  2. let movie = object as Movie
  3. println("Movie: '\(movie.name)', dir. \(movie.director)")
  4. }
  5. // Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
  6. // Movie: 'Moon', dir. Duncan Jones
  7. // Movie: 'Alien', dir. Ridley Scott

為了變為一個更短的形式,下轉someObjects數組為Movie[]類型來代替下轉每一項方式。

  1. for movie in someObjects as Movie[] {
  2. println("Movie: '\(movie.name)', dir. \(movie.director)")
  3. }
  4. // Movie: '2001: A Space Odyssey', dir. Stanley Kubrick
  5. // Movie: 'Moon', dir. Duncan Jones
  6. // Movie: 'Alien', dir. Ridley Scott

Any類型

這裡有個示例,使用 Any 類型來和混合的不同類型一起工作,包括非class類型。它創建了一個可以存儲Any類型的數組 things

  1. var things = Any[]()
  2. things.append(0)
  3. things.append(0.0)
  4. things.append(42)
  5. things.append(3.14159)
  6. things.append("hello")
  7. things.append((3.0, 5.0))
  8. things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))

things 數組包含兩個 Int 值,2個 Double 值,1個 String 值,一個元組 (Double, Double) ,Ivan Reitman 導演的電影「Ghostbusters」。

你可以在 switch cases裡用isas 操作符來發覺只知道是 AnyAnyObject的常量或變量的類型。 下面的示例迭代 things數組中的每一項的並用switch語句查找每一項的類型。這幾種switch語句的情形綁定它們匹配的值到一個規定類型的常量,讓它們可以打印它們的值:

  1. for thing in things {
  2. switch thing {
  3. case 0 as Int:
  4. println("zero as an Int")
  5. case 0 as Double:
  6. println("zero as a Double")
  7. case let someInt as Int:
  8. println("an integer value of \(someInt)")
  9. case let someDouble as Double where someDouble > 0:
  10. println("a positive double value of \(someDouble)")
  11. case is Double:
  12. println("some other double value that I don't want to print")
  13. case let someString as String:
  14. println("a string value of \"\(someString)\"")
  15. case let (x, y) as (Double, Double):
  16. println("an (x, y) point at \(x), \(y)")
  17. case let movie as Movie:
  18. println("a movie called '\(movie.name)', dir. \(movie.director)")
  19. default:
  20. println("something else")
  21. }
  22. }
  23. // zero as an Int
  24. // zero as a Double
  25. // an integer value of 42
  26. // a positive double value of 3.14159
  27. // a string value of "hello"
  28. // an (x, y) point at 3.0, 5.0
  29. // a movie called 'Ghostbusters', dir. Ivan Reitman

注意:
在一個switch語句的case中使用強制形式的類型轉換操作符(as, 而不是 as?)來檢查和轉換到一個明確的類型。在 switch case 語句的內容中這種檢查總是安全的。