Итерация по Enum в Swift 3.0

У меня есть простое перечисление, которое я хотел бы повторить. Для этой цели я принял Sequence и IteratorProtocol, как показано в коде ниже. Кстати, это можно скопировать / вставить на игровую площадку в Xcode 8.

import UIKit

enum Sections: Int {
  case Section0 = 0
  case Section1
  case Section2
}

extension Sections : Sequence {
  func makeIterator() -> SectionsGenerator {
    return SectionsGenerator()
  }

  struct SectionsGenerator: IteratorProtocol {
    var currentSection = 0

    mutating func next() -> Sections? {
      guard let item = Sections(rawValue:currentSection) else {
        return nil
      }
      currentSection += 1
      return item
    }
  }
}

for section in Sections {
  print(section)
}

Но цикл for-in генерирует сообщение об ошибке "Тип" Sections.Type "не соответствует протоколу" Sequence "". Соответствие протокола в моем расширении; Итак, что не так с этим кодом?

Я знаю, что есть другие способы сделать это, но я хотел бы понять, что не так с этим подходом.

Благодарю.

6 ответов

Обратите внимание, что решение Мартина может быть реорганизовано как протокол:

import Foundation

protocol EnumSequence
{
    associatedtype T: RawRepresentable where T.RawValue == Int
    static func all() -> AnySequence<T>
}
extension EnumSequence
{
    static func all() -> AnySequence<T> {
        return AnySequence { return EnumGenerator() }
    }
}

private struct EnumGenerator<T: RawRepresentable>: IteratorProtocol where T.RawValue == Int {
    var index = 0
    mutating func next() -> T? {
        guard let item = T(rawValue: index) else {
            return nil
        }
        index += 1
        return item
    }
}

Затем, учитывая перечисление

enum Fruits: Int {
    case apple, orange, pear
}

ты шлепаешь протокол и шрифт

enum Fruits: Int, EnumSequence {
    typealias T = Fruits
    case apple, orange, pear
}

Fruits.all().forEach({ print($0) }) // apple orange pear

Обновление: Начиная с Swift 4.2, вы можете просто добавить соответствие протокола CaseIterable, смотрите Как перечислить перечисление со строковым типом?,


Вы можете перебрать значение типа, которое соответствует Sequenceпротокол. Следовательно

for section in Sections.Section0 {
  print(section)
}

будет компилировать и дать ожидаемый результат. Но, конечно, это не совсем то, что вы хотите, потому что выбор значения является произвольным, а само значение не требуется в последовательности.

Насколько я знаю, нет способа перебрать сам тип, так что

for section in Sections {
  print(section)
}

компилирует. Это потребует, чтобы "метатип" Sections.Type соответствует Sequence, Возможно, кто-то доказывает, что я не прав.

Что вы можете сделать, это определить метод типа, который возвращает последовательность:

extension Sections {
    static func all() -> AnySequence<Sections> {
        return AnySequence {
            return SectionsGenerator()
        }
    }

    struct SectionsGenerator: IteratorProtocol {
        var currentSection = 0

        mutating func next() -> Sections? {
            guard let item = Sections(rawValue:currentSection) else {
                return nil
            }
            currentSection += 1
            return item
        }
    }

}

for section in Sections.all() {
    print(section)
}

Просто добавьте в enum:static var allTypes: [Sections] = [.Section0, .Section1, .Section2]

И чем вы можете:

Sections.allTypes.forEach { (section) in
            print("\(section)")
}

Это выглядит намного проще:

public protocol EnumSequence {
    init?(rawValue: Int)
}

public extension EnumSequence {

    public static var items: [Self] {
        var caseIndex: Int = 0
        let interator: AnyIterator<Self> = AnyIterator {
            let result = Self(rawValue: caseIndex)
            caseIndex += 1
            return result
        }
        return Array(interator)
    }
}

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

enum MyEnum: Int {
    case One
    case Two
}

extension MyEnum {
    func static allCases() -> [MyEnum] {
        var allCases = [MyEnum]()
        for i in 0..<10000 {
            if let type = MyEnum(rawValue: i) {
                allCases.append(type)
            } else {
                break
            }
        }
        return allCases
    }
}

Затем переберите MyEnum.allCases()..

Итерированный на вышеупомянутых решениях, смотрите ниже протокол, который может быть реализован перечислениями, чтобы добавить последовательность allValues, но также чтобы позволить возможность преобразовать в и из строкового значения.

Очень удобно для строковых перечислений, которые должны поддерживать цель c (там разрешены только перечисления int).

public protocol ObjcEnumeration: LosslessStringConvertible, RawRepresentable where RawValue == Int {
    static var allValues: AnySequence<Self> { get }
}

public extension ObjcEnumeration {
    public static var allValues: AnySequence<Self> {
        return AnySequence {
            return IntegerEnumIterator()
        }
    }

    public init?(_ description: String) {
        guard let enumValue = Self.allValues.first(where: { $0.description == description }) else {
            return nil
        }
        self.init(rawValue: enumValue.rawValue)
    }

    public var description: String {
        return String(describing: self)
    }
}

fileprivate struct IntegerEnumIterator<T: RawRepresentable>: IteratorProtocol where T.RawValue == Int {
    private var index = 0
    mutating func next() -> T? {
        defer {
            index += 1
        }
        return T(rawValue: index)
    }
}

Для конкретного примера:

@objc
enum Fruit: Int, ObjcEnumeration {
    case apple, orange, pear
}

Теперь вы можете сделать:

for fruit in Fruit.allValues {

    //Prints: "apple", "orange", "pear"
    print("Fruit: \(fruit.description)")

    if let otherFruit = Fruit(fruit.description), fruit == otherFruit {
        print("Fruit could be constructed successfully from its description!")
    }
}
Другие вопросы по тегам