Swift:类型转换
每隔一段时间,你都会遇到一些像独角兽一般前沿的情况,迫使你挑战你在当前的时代与领域内所积累的一切知识。而就在刚才我成为了这种情况的受害者。
一个代表危险,另一个代表机会。 援引自五十年代末最知名的美国人之一,三十五年后另一个美国人延续了这个话题:
— 荷马·辛普森 (译者注:分别截取了英文单词 crisis(危险)的前半部分和 opportunity(机会)的后半部分) 危险之前我有一个机会对我负责的应用中的一个 API 的响应进行回顾、重新建模以及代码重构,它接受一个 JSON 类型的数据结构作为参数,其中包含有一个数组类型,混合存储了不同类型的模型。混合存储的原因是这些模型要在一个 "characters" : [ { type: "hero",name: "Jake",power: "Shapeshift" },{ type: "hero",name: "Finn",power: "Grass sword" },{ type: "princess",name: "Lumpy Space Princess",kingdom: "Lumpy Space" },{ type: "civilian",name: "BMO" },name: "Princess Bubblegum",kingdom: "Candy" } ] 如你所见,这些对象在某些方面是相似的,但是又包含了一些其他对象没有的属性。让我们来看看一些可能的解决方案。 类和继承class Character { type: String name: String } class Hero : Character { power: String } class Princess : Character { kingdom: String } class Civilian : Character { } ... struct Model { characters: [Character] } 这是一个完全有效的方案,但是用起来会令人感到沮丧,因为每当我们需要访问那些模型特有的属性时,我们都不得不进行类型检查并且把对象转换成特定的类型。 // 类型检测 if model.characters[indexPath.row] is Hero { print(model.characters[indexPath.row].name) } // 类型检测及转换 if let hero = model.characters[indexPath.row] as? Hero { print(hero.power) } 结构体与协议protocol Character { var type: String { get set } var name: String { get set } } struct Hero : Character { power: String } struct Princess : Character { kingdom: String } struct Civilian : Character { } ... struct Model { characters: [Character] } 因为使用了结构体,所以可以从系统层面优化性能,但是使用结构体仍旧与使用超类 非常 相似。因为我们没有使用协议的任何优势,目前的情况确实也没有什么能利用上的,为了访问类型的特定属性我们仍需有条件地进行类型判断和类型转换。 // Type checking if model.characters[indexPath.row] is Hero { print(model.characters[indexPath.row].name) } // Type checking and Typecasting if let hero = model.characters[indexPath.row] as? Hero { print(hero.power) } 类型转换现在你可能认为我非常鄙视类型转换,但是我没有。只不过在 这种 情况中,类型转换有可能向我们的代码中引入意料之外的副作用。如果向 API 中引入新的类型呢?依照我们对响应的解析,它可能什么都不做,也可能做一些事情。当我们编写代码时,我们应该保证代码总是有意义的,因为 Swift的设计原则是安全性优于其他考虑。 { type: "king" name: "Ice King" power: "Frost" } 机会我们现在面对的情况是:常规的尝试最终得到了相同的结果,所以我们被迫在知识的集合之外进行思考,那么不妨让想法大胆一点或者转换一下……不同的思路 ? 枚举enum Character { case hero,princess,civilian } 因为一个 关联类型enum Character { case hero(Hero) case princess(Princess) case civilian(Civilian) } ... switch characters[indexPath.row] { case .hero(let hero): print(hero.power) case .princess(let princess): print(princess.kingdom) case .civilian(let civilian): print(civilian.name) } 再见了,类型转换...你好,类型转换???! 原始值enum Character : String { // Error case hero(Hero) case princess(Princess) case civilian(Civilian) } 你可能已经注意到了,上例中的 初始化init 所以如果我们不能通过原始值初始化,我们只能定义自己的构造器,因为原本的原始值初始化方式全部是由 枚举类型enum Character { private enum Type : String { case hero,civilian static let key = "type" } } 一个枚举,在另一个枚举内部...这是一个枚举嵌套。
哇,嗯,好吧谢谢再见 (译者注:原文用了一个诙谐的组合词 kthxbai 表示 OK,thank you,goodbye) 在我们使用构造器之前必须预设类型,最好的办法是使用枚举,因为当我们尝试初始化一个对象时,如果出现传入的 允许失败的构造器如果 // 枚举字符 init?(json: [String : AnyObject]) { guard let string = json[Type.key] as? String,type = Type(rawValue: string) else { return nil } switch type { case .hero: guard let hero = Hero(json: json) else { return nil } self = .hero(hero) case .princess: guard let princess = Princess(json: json) else { return nil } self = .princess(princess) case .civilian: guard let civilian = Civilian(json: json) else { return nil } self = .civilian(civilian) } } 解析// 初始化 if let characters = json["characters"] as? [[String : AnyObject]] { self.characters = characters.flatMap { Character(json: $0) } } 当我们解析 JSON 数据的时候,因为 类型转换switch model.characters[indexPath.row] { case .hero(let hero): print(hero.power) case .princess(let princess): print(princess.kingdom) case .civilian(let civilian): print(civilian.name) } 现在你已经掌握这个技巧了,我们让自己的代码风格从意料之外的变成了意料之中的,并且在这个过程中学到了新的东西。在此之前,我们还不得不使用由 福利:使用模糊匹配进行类型转换if case 假设我们有一个函数需要传入一个参数值,该参数是 func printPower(character: Character) { switch character { case .hero(let hero): print(hero.power) default: break } 我们可以使用模式匹配作为替代,并通过使用 func printPower(character: Character) { if case .hero(let hero) = character { print(hero.power) } } 像往常一样,我在 GitHub 上为你提供了一个 playgrounds,同时,如果你手边没有 Xcode 的话,请查阅这个 Gist。 另外,九月份的时候我会在 try! Swift NYC 上发言,到时候会有一帮非常了不起的家伙们到场。非常希望能在那看到你,如果你看到我,记得来打个招呼! 公告我是一个在 Swift 方面比较沉闷的人,但是社区是一个精彩的地方,我真的想为社区的成长贡献力量。我已经决定 2 月 23、24 日在墨尔本举办一个会议,我称之为 Playgrounds。另外,我们有一个 CFP 开放到 11 月 4 日,所以下手要快了! 详情参见 www.PlaygroundsCon.com ,或者在 Twitter 上关注我们:@PlaygroundsCon。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |