Swift 型別轉換 (Type Casting)

型別轉換 (Type Casting) 允許你在執行時檢查一個實例的型別,或者將該實例視為其繼承層級中的另一個父類別或子類別。

Swift 的型別轉換使用 isas 運算子實現。

定義類別層級

為了示範,我們先定義一個基礎類別 MediaItem 和兩個子類別 MovieSong

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

// 建立一個包含 Movie 和 Song 的陣列
// Swift 的型別推斷會將其型別推斷為 [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")
]

檢查型別 (Checking Type)

使用 is 運算子來檢查一個實例是否屬於某個特定子類別。如果不確定,它會回傳 false

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

向下轉型 (Downcasting)

常數或變數在幕後可能實際上屬於一個更具體的子類別。當你相信是這種情況時,你可以嘗試使用 as?as! 運算子將其向下轉型 (Downcast) 至子類別型別。

  • as? (Conditional Downcast):回傳一個 Optional 值。如果轉型失敗,回傳 nil推薦使用這個方式,因為它最安全。
  • as! (Forced Downcast):強制轉型。如果轉型失敗,程式會當機 (Crash)。只有在你百分之百確定型別正確時才使用。
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)")
    }
}

Any 和 AnyObject

Swift 提供了兩個特殊的別名 (Type Aliases) 來處理非特定型別:

  • AnyObject:可以代表任何 Class 型別的實例。
  • Any:可以代表任何型別的實例,包括函式型別、Struct、Enum,甚至是 Int, Double 等基本型別。
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"))

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: // 也可以在 switch 中使用 as 模式匹配
        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)")
    default:
        print("something else")
    }
}
雖然 Any 很靈活,但 Swift 是強型別語言。為了程式碼的安全性與效能,應盡量避免使用 Any,除非你真的需要與非特定型別的 API 互動 (例如某些舊的 Objective-C API)。