首页 > 解决方案 > swift 符合 Stridable 后进入无限循环

问题描述

我有一个这样的枚举:

enum Rank: CaseIterable {
case Ace, King, Queen, ...
}

并想让它符合Stridable. 我试图distance(to other: _)通过获取卡片的索引来实现函数,如下所示:

func distance(to other: Rank) -> Int {
            let indexOfSelf = Rank.allCases.firstIndex(of: self)!
            let indexOfOther = Rank.allCases.firstIndex(of: other)!
            return indexOfOther - indexOfSelf
}

只要我不符合 Stridable,这可以正常工作并且符合预期,例如ace.distanceTo(Queen)结果为 2。但是,一旦我符合Stridable,代码就会进入无限循环并且我得到错误EXC_BAD_ACCESS (code=2, address=x)

这应该发生吗?如果是这样,为什么会这样?

感谢大家的帮助!

我的实现advanced(by n: Int)

func advanced(by n: Int) -> Rank {
            let index = Rank.allCases.firstIndex(of: self)!
            let resultIndex = index + n
            if resultIndex > Rank.allCases.count {
                return .Two
            }
            return Rank.allCases[resultIndex]
}

会导致错误的东西:

call of:ace.distanceTo(Queen)会陷入无限循环

标签: arraysswiftenumsstride

解决方案


Strideable协议的文档中:

重要的

Strideable 协议为取决于 Stride 类型实现的等于 (==) 和小于 (<) 运算符提供默认实现。如果一个符合 Strideable 的类型是它自己的 Stride 类型,它必须提供两个运算符的具体实现以避免无限递归。

在您的情况下Rank,它不是自己的Stride类型,而是调用元素的distance(to:)方法调用。现在有一个特殊的类型实现——正如我们在Stride.swift中的实现中所看到的——调用. 这会导致“无限”递归,并最终导致堆栈溢出。您还可以从堆栈回溯中看到:firstIndex(of:)====Strideabledistance(to:)

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=2, address=0x7ffeef3fffb8)
  * frame #0: 0x0000000100001b4b test`Rank.distance(other=ace, self=king) at main.swift:11
    frame #1: 0x0000000100001fd8 test`protocol witness for Strideable.distance(to:) in conformance Rank at <compiler-generated>:0
    frame #2: 0x00007fff7ac4927b libswiftCore.dylib`static (extension in Swift):Swift.Strideable.== infix(A, A) -> Swift.Bool + 219
    frame #3: 0x0000000100001f56 test`protocol witness for static Equatable.== infix(_:_:) in conformance Rank at <compiler-generated>:0
    frame #4: 0x00007fff7ab25ea9 libswiftCore.dylib`(extension in Swift):Swift.Collection< where A.Element: Swift.Equatable>.firstIndex(of: A.Element) -> Swift.Optional<A.Index> + 905
    frame #5: 0x0000000100001c31 test`Rank.distance(other=ace, self=ace) at main.swift:12:49
    ...
    frame #42256: 0x0000000100001fd8 test`protocol witness for Strideable.distance(to:) in conformance Rank at <compiler-generated>:0
    frame #42257: 0x00007fff7ac4927b libswiftCore.dylib`static (extension in Swift):Swift.Strideable.== infix(A, A) -> Swift.Bool + 219
    frame #42258: 0x0000000100001f56 test`protocol witness for static Equatable.== infix(_:_:) in conformance Rank at <compiler-generated>:0
    frame #42259: 0x00007fff7ab25ea9 libswiftCore.dylib`(extension in Swift):Swift.Collection< where A.Element: Swift.Equatable>.firstIndex(of: A.Element) -> Swift.Optional<A.Index> + 905
    frame #42260: 0x0000000100001c31 test`Rank.distance(other=ace, self=ace) at main.swift:12:49
    frame #42261: 0x0000000100001fd8 test`protocol witness for Strideable.distance(to:) in conformance Rank at <compiler-generated>:0
    frame #42262: 0x00007fff7ac4927b libswiftCore.dylib`static (extension in Swift):Swift.Strideable.== infix(A, A) -> Swift.Bool + 219
    frame #42263: 0x0000000100001f56 test`protocol witness for static Equatable.== infix(_:_:) in conformance Rank at <compiler-generated>:0
    frame #42264: 0x00007fff7ab25ea9 libswiftCore.dylib`(extension in Swift):Swift.Collection< where A.Element: Swift.Equatable>.firstIndex(of: A.Element) -> Swift.Optional<A.Index> + 905
    frame #42265: 0x0000000100001c31 test`Rank.distance(other=queen, self=ace) at main.swift:12:49
    frame #42266: 0x0000000100001830 test`main at main.swift:23:18
    frame #42267: 0x00007fff7b3843d5 libdyld.dylib`start + 1
(lldb) 

正如 Joakim 所说,最简单的解决方案是根据枚举的原始值实现这些方法,这样可以避免(递归)使用==

enum Rank: Int, Strideable {
    case ace, king, queen, jack
    
    func advanced(by n: Int) -> Rank {
        return Self(rawValue: self.rawValue + n)!
    }
    
    func distance(to other: Rank) -> Int {
        return other.rawValue - self.rawValue
    }
}

allCases这也比一次又一次地查找集合中的值更有效。


推荐阅读