быстрые разрывы в бесконечный цикл после соответствия Stridable - PullRequest
3 голосов
/ 09 октября 2019

У меня есть следующее перечисление:

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

и я хочу, чтобы оно соответствовало Stridable. Я попытался реализовать func 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]
}

что-то, что могло бы вызвать ошибку:

вызов: ace.distanceTo(Queen) разорвется в бесконечный цикл

1 Ответ

3 голосов
/ 09 октября 2019

Из документации протокола Strideable :

Важно

Протокол Strideable обеспечивает реализации по умолчанию для равныхОператоры (==) и меньше чем (<), которые зависят от реализаций типа Stride. Если тип, соответствующий Strideable, является его собственным типом Stride, он должен предоставлять конкретные реализации двух операторов, чтобы избежать бесконечной рекурсии. </p>

В вашем случае Rank не является его собственным Stride типом,но метод distance(to:) вызывает firstIndex(of:), что вызывает == для элементов. Теперь == имеет специальную реализацию для Strideable типов, которая - как мы видим из реализации в Stride.swift - вызывает distance(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) 

Как сказал Джоаким, самое простое решение - реализовать эти методы на основе необработанных значений перечислений, что позволяет избежать (рекурсивного) использования ==:

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 снова и снова.

...