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.
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
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)
}
Ajoutez simplement à l'énumération: static var allTypes: [Sections] = [.Section0, .Section1, .Section2]
Et que vous pouvez:
Sections.allTypes.forEach { (section) in
print("\(section)")
}
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)
}
}
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 () ..
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!")
}
}