在Swift 3.0中通过Enum迭代

时间:2016-12-27 21:48:08

标签: swift enums sequence enumeration

我有一个简单的枚举,我想迭代。为此,我采用了Sequence和IteratorProtocol,如下面的代码所示。顺便说一句,这可以复制/粘贴到Xcode 8中的Playground。

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循环会生成错误消息" Type' Sections.Type'不符合协议'序列'" 。 协议一致性在我的扩展中;那么,这段代码出了什么问题?

我知道还有其他方法可以做到这一点,但我想了解这种方法的错误。

感谢。

6 个答案:

答案 0 :(得分:10)

请注意,Martin’s solution可以重构为协议:

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
}

你打了协议和一个typealias:

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

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

答案 1 :(得分:8)

更新:从Swift 4.2开始,您只需添加协议一致性即可 到CaseIterable,请参阅How to enumerate an enum with String type?

您可以迭代符合Sequence的类型的 协议。因此

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

会编译并给出预期的结果。但当然不是 真的是你想要的,因为价值的选择是随意的 价值本身在序列中不需要。

据我所知,没有办法迭代一个类型本身,所以

for section in Sections {
  print(section)
}

编译。那将需要&#34;元型#34; 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)
}

答案 2 :(得分:3)

这看起来简单得多:

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 :(得分:3)

只需添加到枚举: static var allTypes: [Sections] = [.Section0, .Section1, .Section2]

而且您可以:

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

答案 4 :(得分:1)

迭代上面的解决方案,请参阅下面的一个协议,该协议可以通过枚举来实现,以添加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!")
    }
}

答案 5 :(得分:1)

如果你的枚举是基于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()..