Итерация по 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!")
}
}