web-dev-qa-db-fra.com

Itérer à travers un enum dans Swift 3.0

J'ai une simple énumération sur laquelle j'aimerais parcourir. À cette fin, j'ai adopté Sequence and IteratorProtocol comme indiqué dans le code ci-dessous. BTW, cela peut être copié/collé dans un Playground 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 "Le type 'Sections.Type' n'est pas conforme au protocole 'Séquence'" . La conformité du protocole est dans mon extension; alors, quel est le problème avec ce code?

Je sais qu'il existe d'autres moyens de le faire, mais j'aimerais comprendre ce qui ne va pas avec cette approche.

Merci.

5
Phantom59

Notez que la solution de Martin peut être refactorisée en tant que 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é une enum

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

vous giflez le protocole et un typealias:

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

Fruits.all().forEach({ print($0) }) // Apple orange pear
10
Jano

Mise à jour: À partir de Swift 4.2, vous pouvez simplement ajouter la conformité de protocole À CaseIterable, voir Comment énumérer une énumération avec le type String? .


Vous pouvez effectuer une itération sur une valeur d'un type conforme au protocole Sequence . Donc 

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, car le choix de la valeur est arbitraire et la valeur Elle-même n'est pas nécessaire dans la séquence.

Pour autant que je sache, il n'y a aucun moyen de parcourir un type lui-même, de sorte que

for section in Sections {
  print(section)
}

compile. Cela nécessiterait que le "métatype" Sections.Type soit conforme À Sequence. Peut-être que quelqu'un me prouve le contraire.

Ce que vous pouvez faire est de définir une méthode type qui renvoie 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)
}
8
Martin R

Ajoutez simplement à l'énumération: static var allTypes: [Sections] = [.Section0, .Section1, .Section2]

Et que vous pouvez:

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

Cela a l'air tellement 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)
    }
}
3
MonsterSale

Si votre enum est basé sur Int, vous pouvez faire un truc 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 passez en boucle sur MyEnum.allCases () ..

1
Mike S

Itéré des solutions ci-dessus, voir ci-dessous un protocole qui peut être implémenté par des énumérations pour ajouter la séquence allValues ​​mais également pour permettre la conversion en valeur de chaîne. 

Très pratique pour les énumérations de type chaîne qui doivent prendre en charge Objective C (seules les énumérations sont autorisées ici).

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)
    }
}

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!")
    }
}
1
Werner Altewischer