Xcode 9 и Xcode 10 дают разные результаты даже с одной и той же быстрой версией

Я запускаю этот код как на игровой площадке xcode 9.3, так и на xcode 10 beta 3.

import Foundation

public protocol EnumCollection: Hashable {
    static func cases() -> AnySequence<Self>
}

public extension EnumCollection {

    public static func cases() -> AnySequence<Self> {
        return AnySequence { () -> AnyIterator<Self> in
            var raw = 0
            return AnyIterator {
                let current: Self = withUnsafePointer(to: &raw) { $0.withMemoryRebound(to: self, capacity: 1) { $0.pointee } }

                guard current.hashValue == raw else {
                    return nil
                }

                raw += 1
                return current
            }
        }
    }
}

enum NumberEnum: EnumCollection{
    case one, two, three, four
}

Array(NumberEnum.cases()).count

хотя оба используют swift 4.1, они дают мне разные результаты для

в xcode 9.3 размер массива 4

а в xcode 10 beta 3 размер массива равен 0

Я вообще этого не понимаю.


person kr15hna    schedule 06.07.2018    source источник


Ответы (3)


Это недокументированный способ получить последовательность всех значений перечисления, и он работал только случайно с более ранними версиями Swift. Он основан на том, что хеш-значения значений перечисления являются последовательными целыми числами, начиная с нуля.

Это определенно больше не работает со Swift 4.2 (даже если работает в режиме совместимости со Swift 4), потому что значения хэша теперь всегда рандомизированы, см. SE-0206 Hashable Enhancements:

Чтобы сделать хеш-значения менее предсказуемыми, стандартная хэш-функция по умолчанию использует случайное начальное число для каждого выполнения.

Вы можете убедиться в этом с помощью

print(NumberEnum.one.hashValue)
print(NumberEnum.two.hashValue)

который не печатает 0 и 1 с Xcode 10, но некоторые другие значения, которые также меняются при каждом запуске программы.

Для правильного решения Swift 4.2/Xcode 10 см. перечислить перечисление с типом String?:

extension NumberEnum: CaseIterable  { }
print(Array(NumberEnum.allCases).count) // 4
person Martin R    schedule 06.07.2018
comment
спасибо за исправление. просто для моего понимания, так что логическая хэш-функция работает быстро, верно? как это меняется с версией xcode? - person kr15hna; 06.07.2018
comment
@kr15hna: это часть стандартной библиотеки Swift или среды выполнения Swift, и обе они поставляются с приложением Xcode. - person Martin R; 06.07.2018
comment
так что даже если я запускаю 4.1 в xcode 10, он не работает, так что этот 4.1 не совпадает с xcode 9 4.1? - person kr15hna; 06.07.2018
comment
@kr15hna: Нет. Xcode 10 поставляется со Swift 4.2 и имеет режимы совместимости для Swift 3 и 4. - person Martin R; 06.07.2018

Решение для этого приведено ниже для Xcode 10 и Swift 4.2 и выше.

Шаг 1. Создайте протокол EnumIterable.

protocol EnumIterable: RawRepresentable, CaseIterable {
    var indexValue: Int { get }
}

extension EnumIterable where Self.RawValue: Equatable {
    var indexValue: Int {
        var index = -1
        let cases = Self.allCases as? [Self] ?? []
        for (caseIndex, caseItem) in cases.enumerated() {
            if caseItem.rawValue == self.rawValue {
                index = caseIndex
                break
            }
        }
        return index
    }
}

Шаг 2. Расширьте протокол EnumIterator на свои перечисления.

enum Colors: String, EnumIterable {
    case red = "Red"
    case yellow = "Yellow"
    case blue = "Blue"
    case green = "Green"
}

Шаг 3. Используйте свойство indexValue, как при использовании hashValue.

Colors.red.indexValue
Colors.yellow.indexValue
Colors.blue.indexValue
Colors.green.indexValue

Образец отчета о печати и вывод

print("Index Value: \(Colors.red.indexValue), Raw Value: \(Colors.red.rawValue), Hash Value: \(Colors.red.hashValue)")

Вывод: «Значение индекса: 0, исходное значение: красный, значение хеш-функции: 1593214705812839748»

print("Index Value: \(Colors.yellow.indexValue), Raw Value: \(Colors.yellow.rawValue), Hash Value: \(Colors.yellow.hashValue)")

Вывод: «Значение индекса: 1, исходное значение: желтый, значение хеш-функции: -6836447220368660818»

print("Index Value: \(Colors.blue.indexValue), Raw Value: \(Colors.blue.rawValue), Hash Value: \(Colors.blue.hashValue)")

Вывод: «Значение индекса: 2, Исходное значение: Синий, Значение хеш-функции: -8548080225654293616»

print("Index Value: \(Colors.green.indexValue), Raw Value: \(Colors.green.rawValue), Hash Value: \(Colors.green.hashValue)") 

Вывод: «Значение индекса: 3, исходное значение: зеленый, значение хэша: 6055121617320138804»

person Amit Gajjar    schedule 06.02.2019

Если вы используете hashValue перечисления для определения значений регистра (позиции или идентификатора), это неправильный подход, поскольку он не гарантирует возврата последовательных значений int, 0,1,2... Он больше не работает с swift 4.2

Например, если вы используете перечисление следующим образом:

enum AlertResultType {
    case ok, cancel 
}

hashValue этого перечисления может возвращать большое значение int вместо 0 (хорошо) и 1 (отмена).

Поэтому вам может понадобиться более точно объявить тип перечисления и использовать rowValue. Например

enum AlertResultType : Int {
    case ok = 0, cancel = 1
}
person Abhijith    schedule 07.05.2019