【问题标题】:How to implement a 'next' property to a CaseIterable enum in Swift如何在 Swift 中为 CaseIterable 枚举实现“下一个”属性
【发布时间】:2020-04-16 12:58:52
【问题描述】:

我正在尝试将 next 变量添加到枚举中。我可以为特定的枚举这样做,但想对其进行一般扩展,以便我可以通过指定带有协议的枚举从枚举值中获取“下一个”枚举案例,例如 CaseNextIterable

enum MyEnum: CaseIterable { // 'next' here is possible thanks to 'CaseIterable' protocol
    case a, b, c
    // returns the next case, or first if at end of sequence
    // ie. a.next == b, c.next == a
    var next: Self {
        var r: Self!
        for c in Self.allCases + Self.allCases { // not efficient
            if r != nil {
                r = c
                break
            }
            if c == self {
                r = self
            }
        }
        return r
    }
}

【问题讨论】:

    标签: swift enums


    【解决方案1】:

    您可以将CaseIterable 约束Self 扩展到Equatable。然后你只需要在CaseItareble 枚举的firstIndex 之后找到索引并返回该位置的元素。如果索引等于所有情况下的endIndex,则返回第一个元素。

    extension CaseIterable where Self: Equatable {
        private var allCases: AllCases { Self.allCases }
        var next: Self {
            let index = allCases.index(after: allCases.firstIndex(of: self)!)
            guard index != allCases.endIndex else { return allCases.first! }
            return allCases[index]
        }
    }
    

    另一个选项是将AllCases 限制为BidirectionalCollection。这将允许您获取枚举的最后一个元素,检查它是否等于 self 并返回第一个元素,而无需迭代整个集合:

    extension CaseIterable where Self: Equatable, AllCases: BidirectionalCollection {
        var allCases: AllCases { Self.allCases }
        var next: Self {
            guard allCases.last != self else { return allCases.first! }
            return allCases[allCases.index(after: allCases.firstIndex(of: self)!)]
        }
    }
    

    扩展 CaseIterable 的下一个和上一个属性:

    extension CaseIterable {
        typealias Index = AllCases.Index
        var first: Self { allCases.first! }
        private var allCases: AllCases { Self.allCases }
        private static func index(after i: Index) -> Index { allCases.index(after: i) }
    }
    

    extension CaseIterable where AllCases: BidirectionalCollection {
        var last: Self { allCases.last! }
        private static func index(before i: Index) -> Index { allCases.index(before: i) }
    }
    

    extension CaseIterable where Self: Equatable {
        var index: Index { Self.firstIndex(of: self) }
        private static func firstIndex(of element: Self) -> Index { allCases.firstIndex(of: element)! }
    }
    

    extension CaseIterable where Self: Equatable, AllCases: BidirectionalCollection {
        var previous: Self { first == self ? last : allCases[Self.index(before: index)] }
        var next: Self { last == self ? first : allCases[Self.index(after: index)] }
    }
    

    游乐场测试;

    enum Enum: CaseIterable {
        case a,b,c
    }
    
    let value: Enum = .c
    
    let next = value.next  // a
    let next2 = next.next  // b
    let next3 = next2.next // c
    
    let previous = value.previous      // b
    let previous2 = previous.previous  // a
    let previous3 = previous2.previous // c
    

    【讨论】:

    • 第一行除了变短还有什么特殊原因吗?
    • @itMaxence 没有。只是为了方便阅读,避免重复
    【解决方案2】:

    我添加这个来补充 Leo Dabus 的回答,以防人们也需要 previous 扩展。


    编辑:

    我同时添加了nextprevious

    主要区别在于预期的行为。

    对于out of band

    • 此解决方案返回nul
    • Leo Dabus 的解决方案就像chained list
    extension CaseIterable where Self: Equatable {
        var allCases: AllCases { Self.allCases }
    
        /// Using `next` on an empty enum of on the last element returns `nil`
        var next: Self? {
            guard let currentIndex = allCases.firstIndex(of: self) else { return nil }
    
            let index = allCases.index(after: currentIndex)
    
            // ensure we don't go past the last element
            guard index != allCases.endIndex else { return nil }
            return allCases[index]
        }
    
        var previous: Self? {
            guard let currentIndex = allCases.firstIndex(of: self) else { return nil }
    
            // ensure we don't go before the first element
            guard currentIndex != allCases.startIndex else { return nil }
            let index = allCases.index(currentIndex, offsetBy: -1)
    
            return allCases[index]
        }
    }
    

    【讨论】:

    • 我这里也实现了
    • 确实取决于你想要的行为?。尽管这种情况很愚蠢,但如果没有可选的枚举,空枚举会崩溃。但我想我只是在这里偏执狂哈哈
    • 漂亮干净的编辑!由于主要区别在于行为,我也会编辑我的答案
    • 你不能创建一个空的 CaseIterable 枚举
    • 哦,你是对的!您可以声明一个空的 CaseIterable 枚举,但是!无法以任何方式实际创建它的实例。所以空壳上的guard实际上是没用的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-07
    • 2018-10-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多