Swift-型別轉換(Type Casting)(十七)
前言
型別轉換 可以判斷例項的型別,也可以將例項看作是父類或者子類的例項。
型別轉換 在 Swift 中使用 is
和 as
操作符實現,當然也包括後面加歎號 !
的強制展開和後面加問號 ?
的可選型別。這兩個操作符提供了一種簡單達意的方式去檢查值的型別或是轉換它的型別。
- 定義一個類層次作為例子
- 檢查型別
- 向下轉換
Downcasting
Any
和AnyObject
的型別轉換
也可以用型別轉換來檢查一個類是否實現了某個協議。這個先知道就好,後面會有詳細的介紹。
這一小節有幾個比較生疏的名字,譬如類層次、檢查型別、向下轉型等,其實在 Obejctive-C
中,這些看起來陌生的名字我們都是經常使用的,類層次就是 繼承類之後子類和父類之間的關係,檢查型別 is
Obejctive-C
中的 isKindOf(_)
功能類似,而向下轉化就是我們所說的強制轉換,例如下面的示例程式碼,就是將子類強轉為父類:(這個是向上轉換,至於這小節說的向下轉換的例子,一時沒想到,並且下面的示例程式碼對於向下轉換表述的很好。其實主要是先搞明白什麼是轉換。)
UILabel *myLabel = [[UILabel alloc]init];
UIView *myView = (UIView *)myLabel
myView.frame = CGRectMake(10, 10, 100, 100);
只不過在 Swift 換了個方式,使用 as
關鍵字來解決這個問題。
分條詳述
定義一個類層次作為例子
可以將型別轉換用在類和子類的層次結構上,檢查特定類例項的型別並且轉換這個類例項的型別成為這個結構層次中的其他型別。下面的示例程式碼就是定義了一個父類和對應的兩個子類:
// 定義一個基類,媒體資源類,包含一個名字 class MediaItem { var name: String // 假設繼承它的所有子類都有 name 這個屬性 // 構造器 init(mediaName name: String) { self.name = name } } // Movie 子類 class Movie: MediaItem { // 導演屬性 var
上面的幾行程式碼就定義了一個父類和兩個子類,下面建立一個數組常量
library
,包含Movie
和Song
型別資料。注意,這裡並沒有去指定陣列內資料的型別,當然我們知道,陣列內的資料型別必須是一致的,由於
Movie
和Song
都繼承自MediaItem
,所以由內容推斷出[MediaItem]
類作為library
的型別:// 定義一個資源陣列 let library = [Movie(movieName: "美人魚", movieDirector: "周星馳"), Movie(movieName: "我的特工爺爺", movieDirector: "洪金寶"), Song(songName: "Five Hundred Miles", songArtist: "Justin"), Song(songName: "Hello", songArtist: "Adele"), Movie(movieName: "戰狼", movieDirector: "吳京")]
這裡在多說一點,雖然
library
中的資料都是[MediaItem]
型別,但是陣列記憶體儲的媒體依然是Movie
和Song
。如果要迭代這個陣列,依次取出的例項是[MediaItem]
型別的,而不是Movie
和Song
型別。為了讓他們作為原本的型別工作,那麼就需要型別轉換。型別檢查
用型別檢查操作符
is
來檢查一個例項是否屬於特定子型別。如果屬於那個子型別,類檢查操作符返回true
,否則返回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( "movieCount = \(movieCount) , songCount = \(songCount)") // 輸出: movieCount = 3 , songCount = 2
向下轉型
某型別的一個常量或變數可能在幕後屬於另一個子類。當確定這種情況時,就可以嘗試向下轉換到它的子型別,用型別轉換操作符
as!
或者as?
。因為向下轉型可能會失敗,型別轉換操作符帶有兩種不同形式。條件形式
as?
返回一個試圖向下轉成的型別的可選值optional value
。強制形式as!
把試圖向下轉型和強制解包force-unwraps
結果作為一個混合動作。當不確定向下轉型是否可以成功時,用型別轉換的條件形式
as?
。條件形式的型別轉換總是返回一個可選值,並且若向下轉換是不可能的,可選值將是nil
。這使我們能夠檢查向下轉換是否成功。只有確定向下轉化一定成功時,才使用強制形式
as!
。當試圖向下轉型為一個不正確的型別時,強制形式的型別轉化會觸發一個執行時的錯誤。至於什麼時候用條件形式,什麼時候用強制形式,這個需要實際開發中去總結。舉個例子吧,例如自定義的
UICollectionViewCell
,這裡顯然用強制形式更合適些。let myCell = collectionView.cellForItemAtIndexPath(indexPath) as! MyCollectionViewCell
而下面的這個例子,就必須要用條件形式了,因為我們並不確定資料到底是什麼型別的:
// 向下轉換 for item in library { if let movie = item as? Movie { print(movie.name, movie.director) } else if let song = item as? Song { print(song.name, song.artist) } } // 輸出: 美人魚 周星馳 我的特工爺爺 洪金寶 Five Hundred Miles Justin Hello Adele 戰狼 吳京
let movie = item as? Movie
這行程式碼可理解為嘗試將 item 轉換為 Movie 型別。若成功,設定一個新的臨時常量 movie 來儲存返回的可選 Movie
。注意,轉換並沒有真的改變它的例項或它的值。潛在的、根本的例項保持不變,只是簡單地把它作為它被轉換成的類來使用。
Any
和AnyObject
的型別轉換Swift 為不確定的型別提供了兩種特殊的類型別名:
AnyObject
可以代表任何class
型別的例項。Any
可以表示任何型別,包括方法型別function types
。
注意,只有當明確的需要它的行為和功能時才使用
Any
和AnyObject
。在程式碼中使用自己期望的明確的型別往往是更好的,所以不是迫不得已,還是少用這兩個型別的好。AnyObject
型別當我們在工作中使用
Cocoa APIs
,我們一般會接收一個[AnyObject]
型別的陣列,或者說一個任何物件型別的陣列
。這是因為Objective-C
沒有明確的型別化陣列。但是,常常可以從API
提供的資訊中清晰的確定陣列中物件的型別。在這些情況下,可以使用強制形式的型別轉換來向下轉換陣列中每一項 到 比
AnyObject
更明確地型別,不需要條件形式的可選解析optional unwrapping
。下面示例程式碼展示強制形式的型別轉換,個人理解這僅僅是個展示用法的程式碼,實際開發中明明知道陣列內的型別,應該不會用
AnyObject
去替代吧。我在開發中遇到的一種需要使用AnyObject
的情況是開發一個無限輪播控制元件,輪播的資料從外部獲取,但是這個資料並不是唯一的,有可能是本地圖片資料Image
,也可能是一堆的圖片連結String
,也可能是接收到一個數據Model
,這個時候用於接收的資料內部就是使用的AnyObject
,然後再去判斷資料型別,做不同的操作。// 已知陣列內部是 Movie 型別的資料 let someObjects: [AnyObject] = [Movie(movieName: "美人魚", movieDirector: "周星馳"), Movie(movieName: "我的特工爺爺", movieDirector: "洪金寶"), Movie(movieName: "戰狼", movieDirector: "吳京")] // 遍歷時可以直接強制形式的型別轉換 for object in someObjects { let movie = object as! Movie print(movie.name, movie.director) } // 其實還有更簡單的轉化方式,就是直接向下轉換整個陣列 for object in someObjects as! [Movie] { print(object.name, object.director) }
Any
型別這裡僅僅展示一個示例,使用
Any
型別來混合不同的資料型別一起工作,包括方法型別和非class
型別。建立一個可以儲存Any
型別的陣列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)" })
這個陣列包含2個
Int
值,2個Double
值,1個String
值,1個元組(Double, Double)
,1個Movie
,和一個獲取String
值並返回另一個String
值得閉包表示式。然後利用
switch
表示式中的case
語句,並結合is
和as
操作符來運算元組內部的資料,簡單來說就是下面這個樣子的(下面的示例程式碼是全部copy教材中的)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
總結
重點是掌握檢查型別 is
和 向下轉換 as?
、 as!
的用法。