在Swift 3.0中通过Enum迭代

Iterating through an Enum in Swift 3.0

我有一个简单的枚举,我想遍历它。为此,我采用了序列和迭代器协议,如下面的代码所示。顺便说一句,这可以复制/粘贴到xcode 8的操场上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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"部分。type"不符合协议"sequence"。协议一致性在我的扩展中;那么,这个代码有什么问题?

我知道还有其他方法可以做到这一点,但我想知道这种方法有什么问题。

谢谢。


请注意,Martin的解决方案可以重构为一个协议:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
    }
}

然后,给定一个枚举

1
2
3
enum Fruits: Int {
    case apple, orange, pear
}

您将拍打协议和类型别名:

1
2
3
4
5
6
enum Fruits: Int, EnumSequence {
    typealias T = Fruits
    case apple, orange, pear
}

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

更新:从swift 4.2开始,您可以简单地添加协议一致性对于CaseIterable,请参见如何用字符串类型枚举枚举枚举枚举?.

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

1
2
3
for section in Sections.Section0 {
  print(section)
}

将编译并给出预期的结果。但那当然不是你真正想要的是什么,因为价值的选择是任意的,序列中不需要值本身。

据我所知,无法迭代类型本身,因此

1
2
3
for section in Sections {
  print(section)
}

编译。这就要求"元类型"Sections.Type符合到Sequence。也许有人证明我错了。

您可以做的是定义一个返回序列的类型方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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)
}


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

比你能做到的还要多:

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

这看起来简单多了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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的枚举,那么您可以执行这样一个有效但有点脏的技巧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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枚举)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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)
    }
}

例如:

1
2
3
4
@objc
enum Fruit: Int, ObjcEnumeration {
    case apple, orange, pear
}

现在你可以做到:

1
2
3
4
5
6
7
8
9
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!")
    }
}