Swift-类型转换(Type Casting)(十七)
Posted 人散风中
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 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
,包含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!
的用法。
以上是关于Swift-类型转换(Type Casting)(十七)的主要内容,如果未能解决你的问题,请参考以下文章