专栏名称: Cocoa开发者社区
CocoaChina苹果开发中文社区官方微信,提供教程资源、app推广营销、招聘、外包及培训信息、各类沙龙交流活动以及更多开发者服务。
目录
相关文章推荐
51好读  ›  专栏  ›  Cocoa开发者社区

Swift:利用Enum灵活映射多重类型Data model

Cocoa开发者社区  · 公众号  · ios  · 2016-08-22 08:18

正文

▲点击上方“CocoaChina”关注即可免费学习 iOS 开发


文/没故事的卓同学(简书作者)
原文链接:http://www.jianshu.com/p/87255dc14331
英文原文:Swift: Typecasing


一个字段中返回了多种相似的类型


先来看下项目中我遇到的一个情况,服务端在人物中返回了一组数据。这些人物有几个相同的属性,但是又有各自不同的角色各有的属性。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"
},
{
type: "princess",
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]
}


这其实就是项目中我原来使用的方案。但是很快就会觉得有点苦逼,因为使用的时候要不断的类型判断,然后类型转换后才能访问到某个具体类型的属性:


// 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)
}


利用结构体和协议


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]
}


这里我们使用了结构体,解析的性能会好一些。但是看起来和前面类的方案差不多。我们并没有利用上protocol的特点,使用的时候我们还是要进行类型判断:


// 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)
}


类型转换的潜在问题


上面的这种类型转换可能引入潜在的问题。如果后台此时增加了一个类型对代码会产生什么样的影响呢?可能想到这种情况提前做了处理,也可能没有处理导致崩溃。


{
type: "king"
name: "Ice King"
power: "Frost"
}


当我们在写代码的时候,应该考虑到这样的场景,当有新类型出现时能不能友好的提示哪里需要处理呢?毕竟swift的设计目标之一就是更安全的语言。


另外一种可能:Enum


我们如何创建一个包含不同类型数据的数组,然后访问他们的属性的时候不用类型转换呢?


enum Character {
case hero, princess, civilian
}


当switch一个枚举时,每种case都需要被照顾到,所以使用enum可以很好的避免一些潜在的问题。但是如果只是这样依然不够好,我们可以更进一步:


Associated values:关联值


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中增加一个case,你就不会遗漏需要再修改何处的代码,消除了潜在的问题。


Raw Value


enum Character : String { // Error: ?
case hero(Hero)
case princess(Princess)
case civilian(Civilian)
}


你可能会发现这个枚举没有实现RawRepresentable协议,这是因为关联值类型的枚举不能同时遵从RawRepresentable协议,他们是互斥的。


如何初始化


如果实现了RawRepresentable协议,就会自带一个利用raw value 初始化的方法。但是我们现在没有实现这个协议,所以我们需要自定义一个初始化方法。先定义一个内部使用的枚举表示类型:


enum Character {
private enum Type : String {
case hero, princess, civilian
static let key = "type"
}
}


Failable initializers


因为传回来的json可能出现映射失败的情况,比如增加的一个新类型,所以这里的初始化方法是可失败的。


// enum Character
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)
}
}


使用枚举解析json


// Model initialisation
if let characters = json["characters"] as? [[String : AnyObject]] {
self.characters = characters.flatMap { Character(json: $0) }
}


注意这里使用了flatMap。当一条数据的type不在我们已经定义的范围内时,Character(json: [String : AnyObject])返回一个nil。我们当然希望过滤掉这些无法处理的数据。所以使用flatMap,flatMap过程中会抛弃为nil的值,所以这里使用了flapMap。


完成!


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)
}


现在可以像最前面展示的那样使用了。可以告别那些将数组类型声明为 Any, AnyObject或者泛型,继承组合的model,使用时再转换类型的日子了。


One More Thing: 模式匹配


如果只处理枚举中的一种类型,我们会这么写:


func printPower(character: Character) {
switch character {
case .hero(let hero):
print(hero.power)
default:
break
}


然而我们可以利用swift提供的模式匹配,用这种更优雅的写法:


func printPower(character: Character) {
if case .hero(let hero) = character {
print(hero.power)
}
}


github上的源码:playgrounds


END




小编推荐:[掘金]是一个高质量的技术社区,从 Swift 到 React Native,性能优化到动效源码,让你不错过 iOS 开发的每一个技术干货。长按图片二维码识别或者各大应用市场搜索「掘金」,技术干货尽在掌握中。



微信号:CocoaChinabbs


▲长按二维码“识别”关注即可免费学习 iOS 开发

月薪十万、出任CEO、赢娶白富美、走上人生巅峰不是梦

--------------------------------------

商务合作QQ:2408167315

投稿邮箱:[email protected]

推荐文章
骑行西藏  ·  巴马梅子酒,休闲一夏
7 年前
室内设计联盟  ·  龙光玖钻售楼部 深圳城市精神所在
7 年前
激光制造网LaserfairCom  ·  欧洲EPIC光电子技术讲座在深圳举办
7 年前