Swift-类型转换(Type Casting)(十七)

Posted 人散风中

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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! 的用法。

以上是关于Swift-类型转换(Type Casting)(十七)的主要内容,如果未能解决你的问题,请参考以下文章

Python Casting

Python Casting

Swift 中的向下转换选项:as?键入,或作为!类型?

Swift4.2~数组和字典(Array, Dictionary)基本类型转换

swift里 asas!as?区别 T.Type与动态类型

为啥 Swift4 会强制转换 UIButton 数组!到 [UIButton?] 类型?