Itération à travers un Enum dans Swift 3.0
j'ai un énum simple que je voudrais itérer. Dans ce but, J'ai adopté la séquence et IteratorProtocol comme indiqué dans le code ci-dessous. BTW, cela peut être copié/collé à un terrain de jeu dans 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)
}
mais la boucle for-in génère le message d'erreur " Type 'Sections.Type ' ne correspond pas au protocole 'Sequence'" . La conformité au Protocole est dans mon extension; alors, qu'est-ce qui ne va pas avec ce code?
je sais qu'il y a d'autres façons de faire cela, mais j'aimerais comprendre ce qui ne va pas avec cette approche.
Merci.
6 réponses
notez que Martin's solution peut être refactorisé comme un protocole:
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
}
}
puis, donné un enum
enum Fruits: Int {
case apple, orange, pear
}
vous claquez le protocole et une typographie:
enum Fruits: Int, EnumSequence {
typealias T = Fruits
case apple, orange, pear
}
Fruits.all().forEach({ print("151920920") }) // apple orange pear
mise à jour: à partir de Swift 4.2, vous pouvez simplement ajouter la conformité au protocole
à CaseIterable
, voir comment énumérer un enum avec un type de chaîne de caractères? .
vous pouvez itérer sur une valeur d'un type qui se conforme à la Sequence
protocole. Par conséquent
for section in Sections.Section0 {
print(section)
}
compilerait et donnerait le résultat attendu. Mais bien sûr, ce n'est pas vraiment ce que vous voulez, parce que le choix de la valeur est arbitraire et de la valeur elle-même non nécessaire dans la séquence.
autant que je sache, il n'y a aucun moyen d'itérer sur un type lui-même, de sorte que
for section in Sections {
print(section)
}
compile. Cela exigerait que le "metatype " Sections.Type
se conforme
à Sequence
. Peut-être que quelqu'un prouve que j'ai tort.
ce que vous pouvez faire est de définir une méthode de type qui retourne une séquence:
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)
}
il suffit d'ajouter à enum:
static var allTypes: [Sections] = [.Section0, .Section1, .Section2]
et que vous pouvez:
Sections.allTypes.forEach { (section) in
print("\(section)")
}
Cela ressemble donc beaucoup plus simple:
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)
}
}
Itéré sur les solutions ci-dessus, voir ci-dessous un protocole qui peut être mis en œuvre par les énumérations pour ajouter le allValues de la séquence, mais aussi pour permettre la possibilité de convertir vers et à partir de la chaîne de valeur.
très pratique pour les énumérations à chaîne qui doivent supporter l'objectif c (seules les énumérations int y sont autorisées).
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: { "151900920".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)
}
}
pour un exemple concret:
@objc
enum Fruit: Int, ObjcEnumeration {
case apple, orange, pear
}
Maintenant vous pouvez faire:
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!")
}
}
si votre enum est basé sur un Int, vous pouvez faire un tour efficace mais légèrement sale comme celui-ci.
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
}
}
puis boucle sur MyEnum.allCases ()..