今天寫的主題型別轉換 (Type Casting) 是在開發時會經常使用到,最常看到就是使用 as? 或 as! 的符號進行轉換,以及 is 的使用,而這些到底有什麼差別,今天就來做一次完整的介紹。
型別轉換是一種檢查實例型別的方法,或者將該實例視為與自己的類層次結構中不同位置的父類別或子類別。
Swift 中的型別轉換是使用 is 和 as 運算符實現的。這兩個運算符提供了一種簡單而富有表現力的方法來檢查值的型別或將值轉換為其他型別。
我們還可以使用型別轉換來檢查型別是否符合協定。
我們可以使用型別轉換和類和子類的層次結構來檢查特定類實例的型別,並將該實例轉換為同一層次結構中的另一個類。下面的三個代碼片段定義了類的層次結構和包含這些類的實例的數組,為型別轉換的範例。
第一個片段定義了一個名為 MediaItem 的新基礎類。此類為數字媒體庫中顯示的任何型別的項目提供基本功能。具體來說,它宣告了型別 String 的屬性 name 和名稱初始值設定項 init。(假設所有媒體項目,包括所有電影和歌曲,都會有一個名字。)
下一個片段定義了 MediaItem 的兩個子類。第一個子類 Movie 包含有關電影或電影的其他資訊。它在基礎類 MediaItem 的前面添加了一個屬性 director ,並帶有相應的初始值設定項。第二個子類 Song 在基類之上添加了一個屬性 artist 和初始值設定項:
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)
}
}
最後一段代碼建立了一個名為 library 的常數數組,其中包含兩個 Movie 實例和三個 Song 實例。通過數組文字的內容初始化來推斷庫數組的型別。 Swift 的型別檢查器能夠推斷出 Movie 和 Song 有一個共同的父類 MediaItem,因此它為數組 library 推斷出 [MediaItem] 的型別:
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。要將它們作為原始型別使用,需要檢查其型別,或將它們向下轉換為其他型別,如下所述。
使用型別檢查運算符 (is) 檢查實例是否屬於某個子類型別。如果實例屬於該子類型別,則型別檢查運算符返回 true,否則返回 false。
下面的範例定義了兩個變數 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 實例。
某個類型別的常數或變數實際上可能是指子類的實例。在我們認為是這種情況的情況下,我們可以嘗試使用型別轉換運算符 (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。因為 item 是一個 MediaItem 實例,所以它可能是一個 Movie;同樣,它也可能是一首 Song,甚至只是一個基礎 MediaItem。由於這種不確定性,當嘗試向下轉換為子類型別時,型別轉換運算符的 as? 形式返回可選值。item as? Movie 的結果為 Movie? 型別 或 Movie 的可選型別。
當應用於陣列 library 中的 Song 實例時,向下轉換為 Movie 失敗。為了解決這個問題,上面的範例使用可選綁定來檢查 Movie 的可選型別是否實際包含一個值(即找出向下轉換是否成功。)這個可選綁定是寫成 "if let movie = item as? Movie",可以理解為:
「嘗試將 item 作為 Movie 訪問。如果成功,請將名為 movie 的新臨時常數設置為存儲在返回 Movie 可選型別中的值。」
如果向下轉換成功,則屬性 Movie 將用於印出該 Movie 實例的描述,包括其 director 的名稱。類似的原理用於檢查 Song 實例,並在 library 中找到 Song 時印出適當的描述(包括 artist 的姓名)。
型別轉換實際上不會修改實例或改變它的值。基礎實例保持不變;它被簡單地處理和訪問,作為被轉換的類別的實例。
Swift 提供了兩種特殊型別來處理非特定型別:
Any 可以表示任何型別的實例,包括函數型別。AnyObject 可以表示任何類型別的實例。僅當明確需要它們提供的行為和功能時才使用 Any 和 AnyObject。最好具體了解希望在代碼中使用的型別。
下面是使用 Any 處理不同型別混合的範例,包括函數型別和非類型別。該範例創建一個名為 things 的數組,它可以儲存 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,Double) 的元組,電影 “Ghostbusters”,以及一個帶有 String 值並返回另一個 String 值的閉包表達式。
要發現常數或變數的特定型別,只要知道型別為 Any 或 AnyObject,可以在 switch 敘述的情況下使用 is 或 as 模式。下面的範例遍歷數組 things 中的項目,並使用 switch 敘述查詢每個項目的型別。有幾個 switch 語句將它們的匹配值綁定到指定型別的常數,以使其值可以印出:
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的值,則 Swift 會發出警告。如果確實需要將可選值用為Any值,則可以使用as運算符將可選明確地轉換為Any,如下所示。
let optionalNumber: Int? = 3
things.append(optionalNumber) // Warning
things.append(optionalNumber as Any) // No warning