Swift tricks-Enum Associated Va

作者: joshualiyz | 来源:发表于2016-07-28 10:19 被阅读1283次

    Swift tricks系列收集Swift牛逼的patterns和让你代码更加Swifty的tricks,持续更新中……

    Associated Value

    Swift的enum和Object-C中的enum一样都不能拥有属性,但是Swift提供了一个非常强大的功能——Associated Value。它可以让enumcase value存储不同类型的值。
    引用苹果官方的例子:

    enum Barcode {
       case upc(Int, Int, Int, Int)
       case qrCode(String)
    }
    

    我们可以这样创建Barcode

    var productBarcode = Barcode.upc(8, 85909, 51226, 3)
    productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
    

    我们可以通过switch语法将Associated Value取出来:

    switch productBarcode {
       case .upc(let numberSystem, let manufacturer, let product, let check):
          print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
       case .qrCode(let productCode):
          print("QR code: \(productCode).")
    }
    

    Raw Value

    enum可以通过raw value对其进行预填充。例如,我们可以用一个Int类型的rawValue来表示硬币的正反面:

    enum Coin:Int{
        case Head = 1
        case Tail
    }
    

    Coin.Head.rawValue为1,Coin.Tail.rawValue为2。
    我们也可以用String类型的rawValue来表示Icon:

    rawValue.png

    我们甚至可以直接通过rawValue初始化enum

    rawValue init.png

    我们可以在定义enum的时候指定rawValue的类型(一般会是Int, String, Character,Float等)。如果不指定,那么就没有rawValue属性。如下面的Coin.Head就没有rawValue。

    enum case cannot have a raw value if the enum does not have a raw type

    enum Coin{
        case Head
        case Tail
    }
    

    rawValue的本质是一个名为RawRepresentable的protocol:

    public protocol RawRepresentable {
        associatedtype RawValue
        public init?(rawValue: Self.RawValue)
        public var rawValue: Self.RawValue { get }
    }
    

    Int, String, Character, Float等都实现了这个protocol。


    下面我们会通过一个例子来深入理解Associated ValueRaw Value

    举个🌰

    假设我们在做一个学生的评分系统,我们需要一个enum来表示学生的成绩:

    enum Score {
        case Fail
        case Pass
        case Good
        case Perfect
    }
    
    var input = 70;
    let score : Score
    
    if(input < 60){
        score = .Fail
    }else if(input < 80){
        score = .Pass
    }else if(input < 90){
        score = .Good
    }else{
        score = .Perfect
    }
    

    上面的做法不怎么优雅,我们可以这样做:

    enum Score2 {
        case Fail
        case Pass
        case Good
        case Perfect
        
        init(_ score : Int){
            if(score < 60){
                self = .Fail
            }else if(score < 80){
                self = .Pass
            }else if(score < 90){
                self = .Good
            }else{
                self = .Perfect
            }
        }
    }
    
    let score2 = Score2(85)
    

    这样一来,判断逻辑就统一了,也更容易维护。

    但是需求往往会比较复杂,例如老师在宣读成绩的时候需要写评语,另外会表扬成绩优秀的学生:

    func writeComment(score : Int) -> () {
        var comment : String
        switch Score2(score) {
        case .Fail:
            comment = "表现不好,要努力啊!"
        case .Pass:
            comment = "加油!"
        case .Good:
            comment = "不错!"
        case .Perfect:
            comment = "非常好!"
        }
        print("comment:\(comment), score:\(score)")
    }
    
    func applauseStudent(score : Int) -> () {
        switch Score2(score) {
        case .Perfect:
            print("你是我的骄傲, 你考了\(score)")
        default: break
        }
    }
    
    func announceScore(score : Int) -> () {
        writeComment(score)
        applauseStudent(score)
    }
    

    writeCommentapplauseStudent两个方法都用到了scoreenum,于是我们对同一个enum初始化了两次(虽然不是什么大事,但是我们还是要精益求精)。

    改成下面这样?

    方案2

    func writeComment(score : Int, grade : Score2) -> ()
    func applauseStudent(score : Int, grade : Score2) -> ()
    

    没次传两个参数,解决了问题,但是有点ugly。有没有更Swifty的方法?

    方案3

    我们可以用associated value,将分数存储在enum中:

    enum Score2 {
        case Fail(Int)
        case Pass(Int)
        case Good(Int)
        case Perfect(Int)
        
        init(_ score : Int){
            if(score < 60){
                self = .Fail(score)
            }else if(score < 80){
                self = .Pass(score)
            }else if(score < 90){
                self = .Good(score)
            }else{
                self = .Perfect(score)
            }
        }
    }
    
    func writeComment(score : Score2) -> () {
        var comment : String
        var grade : Int
        switch score {
        case .Fail(let g):
            comment = "表现不好,要努力啊!"
            grade = g
        case .Pass(let g):
            comment = "加油!"
            grade = g
        case .Good(let g):
            comment = "不错!"
            grade = g
        case .Perfect(let g):
            comment = "非常好!"
            grade = g
        }
        print("comment:\(comment), score:\(grade)")
    }
    
    func applauseStudent(score : Score2) -> () {
        switch score {
        case .Perfect(let grade):
            print("你是我的骄傲, 你考了\(grade)")
        default: break
        }
    }
    
    func announceScore(score : Int) -> () {
        let s = Score2(score)
        writeComment(s)
        applauseStudent(s)
    }
    

    方案4

    方案3看起来很不错,但是我们在switch中做了大量的值提取操作。下面来介绍终极方案:associated value + raw value

    enum Score3: RawRepresentable{
        case Fail(Int)
        case Pass(Int)
        case Good(Int)
        case Perfect(Int)
        
        typealias RawValue = Int
    
        var rawValue: RawValue {
            var grade : Int
            switch self {
            case .Fail(let g):
                grade = g
            case .Pass(let g):
                grade = g
            case .Good(let g):
                grade = g
            case .Perfect(let g):
                grade = g
            }
            return grade
        }
        init?(rawValue: RawValue) {
            if(rawValue < 60){
                self = .Fail(rawValue)
            }else if(rawValue < 80){
                self = .Pass(rawValue)
            }else if(rawValue < 90){
                self = .Good(rawValue)
            }else{
                self = .Perfect(rawValue)
            }
        }
    }
    
    func writeComment(score : Score3) -> () {
        var comment : String
        switch score {
        case .Fail:
            comment = "表现不好,要努力啊!"
        case .Pass:
            comment = "加油!"
        case .Good:
            comment = "不错!"
        case .Perfect:
            comment = "非常好!"
        }
        print("comment:\(comment), score:\(score.rawValue)")
    }
    
    func applauseStudent(score : Score3) -> () {
        switch score {
        case .Perfect:
            print("你是我的骄傲, 你考了\(score.rawValue)")
        default: break
        }
    }
    
    func announceScore(score : Int) -> () {
        let s = Score3(rawValue: score)
        writeComment(s!)
        applauseStudent(s!)
    }
    

    参考:Swift: Raw{Not}Representable enum

    相关文章

      网友评论

        本文标题:Swift tricks-Enum Associated Va

        本文链接:https://www.haomeiwen.com/subject/kcmnjttx.html