1. 程式人生 > >Swift-型別轉換(Type Casting)(十七)

Swift-型別轉換(Type Casting)(十七)

前言

型別轉換 可以判斷例項的型別,也可以將例項看作是父類或者子類的例項。

型別轉換 在 Swift 中使用 isas 操作符實現,當然也包括後面加歎號 ! 的強制展開和後面加問號 ? 的可選型別。這兩個操作符提供了一種簡單達意的方式去檢查值的型別或是轉換它的型別。

  • 定義一個類層次作為例子
  • 檢查型別
  • 向下轉換 Downcasting
  • AnyAnyObject 的型別轉換

也可以用型別轉換來檢查一個類是否實現了某個協議。這個先知道就好,後面會有詳細的介紹。

這一小節有幾個比較生疏的名字,譬如類層次、檢查型別、向下轉型等,其實在 Obejctive-C 中,這些看起來陌生的名字我們都是經常使用的,類層次就是 繼承類之後子類和父類之間的關係,檢查型別 is

關鍵字和 Obejctive-C 中的 isKindOf(_) 功能類似,而向下轉化就是我們所說的強制轉換,例如下面的示例程式碼,就是將子類強轉為父類:(這個是向上轉換,至於這小節說的向下轉換的例子,一時沒想到,並且下面的示例程式碼對於向下轉換表述的很好。其實主要是先搞明白什麼是轉換。)

 UILabel *myLabel = [[UILabel alloc]init];   
 UIView *myView = (UIView *)myLabel
 myView.frame = CGRectMake(10, 10, 100, 100);

只不過在 Swift 換了個方式,使用 as 關鍵字來解決這個問題。

分條詳述

  1. 定義一個類層次作為例子

    可以將型別轉換用在類和子類的層次結構上,檢查特定類例項的型別並且轉換這個類例項的型別成為這個結構層次中的其他型別。下面的示例程式碼就是定義了一個父類和對應的兩個子類:

    //  定義一個基類,媒體資源類,包含一個名字
    class MediaItem {
    var name: String   //  假設繼承它的所有子類都有 name 這個屬性
    //  構造器
    init(mediaName name: String) {
        self.name = name
    }
    }
    //  Movie 子類
    class Movie: MediaItem {
    //  導演屬性
    var
    director: String // 構造器,作品名和導演名 init(movieName name: String, movieDirector director: String ) { self.director = director super.init(mediaName: name) // 繼承 } } // Song 子類 class Song: MediaItem { // 藝術家屬性 var artist: String // 構造器 init(songName name: String, songArtist artist: String) { self.artist = artist super.init(mediaName: name) } }

    上面的幾行程式碼就定義了一個父類和兩個子類,下面建立一個數組常量 library ,包含 MovieSong 型別資料。

    注意,這裡並沒有去指定陣列內資料的型別,當然我們知道,陣列內的資料型別必須是一致的,由於 MovieSong 都繼承自 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] 型別,但是陣列記憶體儲的媒體依然是 MovieSong 。如果要迭代這個陣列,依次取出的例項是 [MediaItem] 型別的,而不是 MovieSong 型別。為了讓他們作為原本的型別工作,那麼就需要型別轉換。

  2. 型別檢查

    用型別檢查操作符 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
  3. 向下轉型

    某型別的一個常量或變數可能在幕後屬於另一個子類。當確定這種情況時,就可以嘗試向下轉換到它的子型別,用型別轉換操作符 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

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

  4. AnyAnyObject 的型別轉換

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

    • AnyObject 可以代表任何 class 型別的例項。
    • Any 可以表示任何型別,包括方法型別 function types

    注意,只有當明確的需要它的行為和功能時才使用 AnyAnyObject 。在程式碼中使用自己期望的明確的型別往往是更好的,所以不是迫不得已,還是少用這兩個型別的好。

    • 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 語句,並結合 isas 操作符來運算元組內部的資料,簡單來說就是下面這個樣子的(下面的示例程式碼是全部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! 的用法。

吃多了狗糧,好累