原稿链接: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

迎接关注我的微博:@没故事的卓同学

相关文章

网站地图xml地图