如何枚举String类型的枚举?


530
enum Suit: String {
    case spades = "♠"
    case hearts = "♥"
    case diamonds = "♦"
    case clubs = "♣"
}

例如,我该怎么做:

for suit in Suit {
    // do something with suit
    print(suit.rawValue)
}

结果示例:

♠
♥
♦
♣

在什么情况下您不知道类型?
mtbennett 2014年

没错,在这种情况下,它是String类型。
Lucien 2014年

斯威夫特还没有反思...
Sulthan

22
将它们称为枚举是否具有讽刺意味,但在Swift中进行枚举却非常烦人吗?
Charlton Provatas '18

3
@CharltonProvatas如果这是Swift的唯一缺点,那我就把它称为一天。看看有多少人为此提供不同的解决方法,我只是在my键盘。
qwerty_so

Answers:


266

迅捷4.2+

Swift 4.2(带有Xcode 10)开始,只需添加协议一致性CaseIterable即可从中受益allCases。要添加此协议一致性,您只需要在某处写:

extension Suit: CaseIterable {}

如果枚举是您自己的,则可以直接在声明中指定一致性:

enum Suit: String, CaseIterable { case spades = "♠"; case hearts = "♥"; case diamonds = "♦"; case clubs = "♣" }

然后,以下代码将打印所有可能的值:

Suit.allCases.forEach {
    print($0.rawValue)
}

与早期Swift版本(3.x和4.x)的兼容性

如果需要支持Swift 3.x或4.0,则可以通过添加以下代码来模仿Swift 4.2的实现:

#if !swift(>=4.2)
public protocol CaseIterable {
    associatedtype AllCases: Collection where AllCases.Element == Self
    static var allCases: AllCases { get }
}
extension CaseIterable where Self: Hashable {
    static var allCases: [Self] {
        return [Self](AnySequence { () -> AnyIterator<Self> in
            var raw = 0
            var first: Self?
            return AnyIterator {
                let current = withUnsafeBytes(of: &raw) { $0.load(as: Self.self) }
                if raw == 0 {
                    first = current
                } else if current == first {
                    return nil
                }
                raw += 1
                return current
            }
        })
    }
}
#endif

@DmitryPetukhov我很乐意为您提供帮助,但是:(1)您确定获得了最新版本的代码吗?(一个月前解决了一些崩溃问题),并且(2)请提供您的可以重现崩溃的自定义类型的MCVE,以及您的Xcode版本。
心教堂

对于调试版本,这对我来说效果很好,但是当我创建发行版并上传到TestFlight时,它就会崩溃。苹果是否以某种方式将其剥离?
丹尼尔·伍德

1
您的版本似乎比CaseIterator的内置版本好。我可以使用您的版本扩展在另一个文件中定义的枚举。如果将扩展中的allCases公开,则还可以扩展在不同框架中定义的枚举。
adamfowlerphoto '19

1
@Cyber​​Mew我已经更新了答案以澄清它。Swift书和Xcode 10发行说明中提到CaseIterable在我的回答之后,并且它们使用了简化的示例,其中枚举不支持String,而不是当前的Stack Overflow问题。
心教堂

1
我想强调“#if!swift(> = 4.2)”的重要性。如果您在swift 4.2之前编写代码,而忘记了删除下面的代码“#if!swift(> = 4.2)”,则当您使用Xcode版本11.4在测试设备上本地构建时,一切都会好起来的。但是,当您从应用程序商店下载应用程序或进行试飞时,这段代码将使您的应用程序崩溃。这种错误很难发现或调试。
奥利弗·张

524

这篇文章与这里有关https://www.swift-studies.com/blog/2014/6/10/enumerating-enums-in-swift

本质上,建议的解决方案是

enum ProductCategory : String {
     case Washers = "washers", Dryers = "dryers", Toasters = "toasters"

     static let allValues = [Washers, Dryers, Toasters]
}

for category in ProductCategory.allValues{
     //Do something
}

188
很好,但是...您必须两次输入枚举的元素-一次输入枚举,一次输入allValues。并不像人们希望的那样优雅。
杰伊·伊曼

2
同意“但是” ...但是,如文章中所述,可能存在一个问题,枚举实际上是一个集合,因此是无序的。请注意...在其中定义的排序条件不是一个不好的开始!
rougeExciter 2014年

3
在Java中,编译器会为您执行此操作,也许Swift 2.0也会执行此操作。特别是在Java中,所有枚举都获得一个描述(Java中的toString)方法,该方法将String作为案例名称(Washers,...),并自动创建一组案例。Java还为您提供位置索引。如我所说,也许是Swift 2.0。
2014年

1
理想情况下,您可以执行类似于c#实现的操作,Enum.Values(typeof(FooEnum))但是可以作为扩展方法(例如map或reduce)公开。 FooEnum.values() :: values(EnumType -> [EnumType])
rodrigoelp 2014年

1
本文最后提出了一个很好的观点,即将每个枚举值放在具有单元测试的allValues数组中。但是,我仍然可以看到有人添加了更多元素,但是在单元测试中没有强制执行它们,这仍然使我们回到开始时就无法确定所有枚举值都保留在allValues中。
DonnaLea

278

我制作了一个实用程序函数iterateEnum()来迭代任意enum类型的大小写。

这是示例用法:

enum Suit: String {
    case Spades = "♠"
    case Hearts = "♥"
    case Diamonds = "♦"
    case Clubs = "♣"
}

for f in iterateEnum(Suit) {
    println(f.rawValue)
}

哪个输出:

♠
♥
♦
♣

但是,这仅用于调试或测试目的:这依赖于多个未记录的Swift1.1编译器行为,因此,使用此方法后果自负。

这是代码:

func iterateEnum<T: Hashable>(_: T.Type) -> GeneratorOf<T> {
    var cast: (Int -> T)!
    switch sizeof(T) {
        case 0: return GeneratorOf(GeneratorOfOne(unsafeBitCast((), T.self)))
        case 1: cast = { unsafeBitCast(UInt8(truncatingBitPattern: $0), T.self) }
        case 2: cast = { unsafeBitCast(UInt16(truncatingBitPattern: $0), T.self) }
        case 4: cast = { unsafeBitCast(UInt32(truncatingBitPattern: $0), T.self) }
        case 8: cast = { unsafeBitCast(UInt64($0), T.self) }
        default: fatalError("cannot be here")
    }

    var i = 0
    return GeneratorOf {
        let next = cast(i)
        return next.hashValue == i++ ? next : nil
    }
}

基本思想是:

  • 当案例的计数为时,的内存表示形式enum(不包括enum带有关联类型的s)只是案例的索引2...256,它与UInt8,when 257...65536,thes UInt16等等是相同的。因此,它可以unsafeBitcast来自相应的无符号整数类型。
  • .hashValue 枚举值的和与案例的索引相同。
  • .hashValue无效索引进行位播的枚举值的总和为0

已针对Swift2进行了修订,并从@Kametrixom的答案中实现了投放创意:

func iterateEnum<T: Hashable>(_: T.Type) -> AnyGenerator<T> {
    var i = 0
    return anyGenerator {
        let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
        return next.hashValue == i++ ? next : nil
    }
}

为Swift3修订:

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafePointer(to: &i) {
            $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee }
        }
        if next.hashValue != i { return nil }
        i += 1
        return next
    }
}

为Swift3.0.1修订:

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafeBytes(of: &i) { $0.load(as: T.self) }
        if next.hashValue != i { return nil }
        i += 1
        return next
    }
}

20
很棒,而且是回答问题的唯一答案!但是,是的...不会碰它!+1值得努力!
dotToString 2015年

我只是发布基本上以相同方式工作的答案(仅在以后看到此答案)。它使用Swift 2.0 beta 6和该语言的现代功能。
Kametrixom 2015年

2
Swift 3版本效果很好。只需修改一下用法即可:对于iterateEnum(Suit.self){print(f.rawValue)}中的f
Gene De Lisa

16
+1这是非常出色的。恕我直言,它也太聪明了,无法使用,正如它在Swift的每个主要版本更改中都明显中断一样。以笔者的信用,雨燕3.0版本做了一个月前,斯威夫特3来测试出来......如果你要采取这个答案,了解这一切withUnsafePointer withMemoryReboundpointee东西,然后通过各种手段使用。否则,我会避免它。
Dan Rosenstark '16

5
我只想添加此内容,现在它已在swift 4中被打破了,但是仅在linux上,所以对上述注释+1认为这太聪明了,无法使用。
安德鲁·普鲁默

130

其他解决方案也可行,但是它们都假设了例如可能的等级和西服的数量,或者首位和最后一位可能是多少。没错,在可预见的将来,一副纸牌的布局可能不会有太大变化。但是,总的来说,编写尽可能少的假设的代码更加整洁。我的解决方案:

我向Suit枚举添加了原始类型,因此可以Suit(rawValue:)用来访问Suit案例:

enum Suit: Int {
    case Spades = 1
    case Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
        switch self {
            case .Spades:
                return "spades"
            case .Hearts:
                return "hearts"
            case .Diamonds:
                return "diamonds"
            case .Clubs:
                return "clubs"
        }
    }
    func color() -> String {
        switch self {
        case .Spades:
            return "black"
        case .Clubs:
            return "black"
        case .Diamonds:
            return "red"
        case .Hearts:
            return "red"
        }
    }
}

enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King
    func simpleDescription() -> String {
        switch self {
            case .Ace:
                return "ace"
            case .Jack:
                return "jack"
            case .Queen:
                return "queen"
            case .King:
                return "king"
            default:
                return String(self.rawValue)
        }
    }
}

下面执行Card的createDeck()方法。init(rawValue:)是一个失败的初始化程序,并返回一个可选的。通过在两个while语句中解开并检查其值,无需假设Rank或的数量Suit

struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
    func createDeck() -> [Card] {
        var n = 1
        var deck = [Card]()
        while let rank = Rank(rawValue: n) {
            var m = 1
            while let suit = Suit(rawValue: m) {
                deck.append(Card(rank: rank, suit: suit))
                m += 1
            }
            n += 1
        }
        return deck
    }
}

这是怎么称呼 createDeck方法的方法:

let card = Card(rank: Rank.Ace, suit: Suit.Clubs)
let deck = card.createDeck()

12
我在这个主题的各个主题上看到的绝对最佳答案。十分优雅。这适用于Int类型枚举,但我想知道如何遍历其他类型(字符串,自定义类型等)。
杰伊·伊曼

3
这绝对是最好的解决方案。要注意的一件事。在书中的示例中,它不像sdduursma那样具有“ case Spades = 1”。起初我没听懂。这是一种选择,或者您可以只使用“ var m = 0”
Joshua Goossen 2014年

10
这假设原始值是连续的。如果不正确,例如枚举表示位掩码标志,则循环会过早退出。
2014年

1
此解决方案假定您可以修改的定义Suit。在此示例中,您可以这样做,但是练习的目的是使您与enums来自外部的人一起工作。
Josh J

1
我唯一要抱怨的是我不能将其称为静态方法,而必须先创建卡对象。
2016年

76

我偶然发现了位和字节,并创建了一个扩展,后来我发现它的扩展名与@rintaro的答案非常相似。它的用法如下:

enum E : EnumCollection {
    case A, B, C
}

Array(E.cases())    // [A, B, C]

值得注意的是,它可以在没有关联值的任何枚举中使用。请注意,这不适用于没有大小写的枚举。

@rintaro的答案一样,此代码使用枚举的基本表示形式。此表示形式尚未记录,将来可能会更改,这会破坏它。 我不建议在生产中使用此方法。

代码(Swift 2.2,Xcode 7.3.1,不适用于Xcode 10):

protocol EnumCollection : Hashable {}
extension EnumCollection {
    static func cases() -> AnySequence<Self> {
        typealias S = Self
        return AnySequence { () -> AnyGenerator<S> in
            var raw = 0
            return AnyGenerator {
                let current : Self = withUnsafePointer(&raw) { UnsafePointer($0).memory }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }
    }
}

代码(Swift 3,Xcode 8.1,不适用于Xcode 10):

protocol EnumCollection : Hashable {}
extension EnumCollection {
    static func cases() -> AnySequence<Self> {
        typealias S = Self
        return AnySequence { () -> AnyIterator<S> in
            var raw = 0
            return AnyIterator {
                let current : Self = withUnsafePointer(to: &raw) { $0.withMemoryRebound(to: S.self, capacity: 1) { $0.pointee } }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }
    }
}

我不知道为什么需要typealias,但是编译器抱怨没有它。


10
这个答案甚至比我的答案还要好,尤其是在铸件方面:)
rintaro

但是我认为这仅适用于小端环境?
rintaro

5
Xcode 8 beta 6再次改变了这一点!我收到以下错误`“INIT”是不可用:使用“withMemoryRebound(到:容量:__)”暂时查看内存作为另一个布局兼容type.`
困惑Vorlon

1
@ConfusedVorlon:看上面的答案通过@Rintaro:更换withUnsafePointer... pointee}通过withUnsafePointer(to: &i) { $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee } }
斯特凡

1
从Xcode 10开始,这似乎不再起作用(我知道在Swift 4.2中将不再需要),但是在Xcode 10中使用Swift 4(.1)时,该代码不再起作用(原始值不相等)
benrudhart

26

您可以通过实现ForwardIndexType协议来遍历枚举。

ForwardIndexType协议要求您定义一个successor()功能来逐步遍历元素。

enum Rank: Int, ForwardIndexType {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King

    // ... other functions

    // Option 1 - Figure it out by hand
    func successor() -> Rank {
        switch self {
            case .Ace:
              return .Two
            case .Two:
              return .Three

            // ... etc.

            default:
              return .King
        }
    }

    // Option 2 - Define an operator!
    func successor() -> Rank {
        return self + 1
    }
}

// NOTE: The operator is defined OUTSIDE the class
func + (left: Rank, right: Int) -> Rank {
    // I'm using to/from raw here, but again, you can use a case statement
    // or whatever else you can think of

    return left == .King ? .King : Rank(rawValue: left.rawValue + right)!
}

在打开或关闭范围(..<...)上进行迭代将在内部调用该successor()函数,该函数可让您编写以下代码:

// Under the covers, successor(Rank.King) and successor(Rank.Ace) are called to establish limits
for r in Rank.Ace...Rank.King {
    // Do something useful
}

2
给定在一定范围内使用时所得到的语法,我发现这是对该问题最“适当”的答案,即使是最“优雅”(相对于此处的其他选项而言,也是最必要的额外代码)(该语法是我如果没有可枚举的解决方法枚举,则有望做到)。谢谢!尽管值得注意的是,如果运算符重载被使用在除了successor()之外的其他地方(这似乎很诱人),那么显然强制展开是危险的。此外,中缀似乎不必要...?
iOS Gamer 2014年

更新的答案以反映最新的Swift语言规范
RndmTsk 2014年

正确定义的successor()方法(第一种选择)将消除enum对具有关联类型的需求。+1
nhgrif 2014年

1
但是,这个优雅的答案不适用于String枚举吗?
阿里

最“合适” /最佳实践的解决方案!+ 1-ed
小清庞-明日香贤治

18

现在,这个问题要容易得多。这是我的Swift 4.2解决方案:

enum Suit: Int, CaseIterable {
  case None
  case Spade, Heart, Diamond, Club

  static let allNonNullCases = Suit.allCases[Spade.rawValue...]
}

enum Rank: Int, CaseIterable {
  case Joker
  case Two, Three, Four, Five, Six, Seven, Eight
  case Nine, Ten, Jack, Queen, King, Ace

  static let allNonNullCases = Rank.allCases[Two.rawValue...]
}

func makeDeck(withJoker: Bool = false) -> [Card] {
  var deck = [Card]()
  for suit in Suit.allNonNullCases {
    for rank in Rank.allNonNullCases {
      deck.append(Card(suit: suit, rank: rank))
    }
  }
  if withJoker {
    deck.append(Card(suit: .None, rank: .Joker))
  }
  return deck
}

4.2之前的版本:

我喜欢在Swift中找到“ 列表理解 ”后将其组合在一起的解决方案

它使用Int raws而不是Strings,但是它避免键入两次,它允许自定义范围,并且不对原始值进行硬编码。

这是我原始解决方案的Swift 4版本,但请参见上面的4.2改进:

enum Suit: Int {
  case None
  case Spade, Heart, Diamond, Club

  static let allRawValues = Suit.Spade.rawValue...Suit.Club.rawValue
  static let allCases = Array(allRawValues.map{ Suit(rawValue: $0)! })
}
enum Rank: Int {
  case Joker
  case Two, Three, Four, Five, Six
  case Seven, Eight, Nine, Ten
  case Jack, Queen, King, Ace

  static let allRawValues = Rank.Two.rawValue...Rank.Ace.rawValue
  static let allCases = Array(allRawValues.map{ Rank(rawValue: $0)! })
}
func makeDeck(withJoker: Bool = false) -> [Card] {
  var deck = [Card]()
  for suit in Suit.allCases {
    for rank in Rank.allCases {
      deck.append(Card(suit: suit, rank: rank))
    }
  }
  if withJoker {
    deck.append(Card(suit: .None, rank: .Joker))
  }
  return deck
}

哦,我现在知道我的基本上与Sutean Rutjanalard的一样。
adazacom 2015年

1
实际上,我更喜欢您的实现。我认为这更清楚!1票。实际上,投票最多的答案太聪明了,将来肯定会打破。您保证将来会保持稳定。
jvarela

17

原则上,假设您在枚举的情况下不使用原始值分配,则可以这样做:

enum RankEnum: Int {
  case Ace
  case One
  case Two
}

class RankEnumGenerator: Generator {
    var i = 0
    typealias Element = RankEnum
    func next() -> Element? {
        let r = RankEnum.fromRaw(i)
        i += 1
        return r
    }
}

extension RankEnum {
    static func enumerate() -> SequenceOf<RankEnum> {
        return SequenceOf<RankEnum>({ RankEnumGenerator() })
    }
}

for r in RankEnum.enumerate() {
    println("\(r.toRaw())")
}

7
这很好,但仅适用于从0开始的连续Integer枚举
Robert

@Robert,正如我在上面的评论中指出的那样:“对于枚举,您不使用原始值赋值”
Alfa07年

是的-不要使用原始值,也不要将基础类型设置为int。迅速地,您不需要西装示例中的枚举类型。enum ItWontWorkForThisEnum {case a, b, c}
罗伯特·

如果元组与枚举案例相关联,这将如何解决该问题?
rougeExciter 2014年

您不能很容易地将元组与枚举关联。
nhgrif 2014年

13

如果给枚举一个原始的Int值,它将使循环变得更加容易。

例如,您可以anyGenerator用来获取可以枚举所有值的生成器:

enum Suit: Int, CustomStringConvertible {
    case Spades, Hearts, Diamonds, Clubs
    var description: String {
        switch self {
        case .Spades:   return "Spades"
        case .Hearts:   return "Hearts"
        case .Diamonds: return "Diamonds"
        case .Clubs:    return "Clubs"
        }
    }
    static func enumerate() -> AnyGenerator<Suit> {
        var nextIndex = Spades.rawValue
        return anyGenerator { Suit(rawValue: nextIndex++) }
    }
}
// You can now use it like this:
for suit in Suit.enumerate() {
    suit.description
}
// or like this:
let allSuits: [Suit] = Array(Suit.enumerate())

但是,这似乎是一个相当普遍的模式,如果我们可以通过简单地遵循一个协议就可以使任何枚举类型成为可枚举的,那岂不是很好吗?有了Swift 2.0和协议扩展,现在我们可以了!

只需将其添加到您的项目中:

protocol EnumerableEnum {
    init?(rawValue: Int)
    static func firstValue() -> Int
}
extension EnumerableEnum {
    static func enumerate() -> AnyGenerator<Self> {
        var nextIndex = firstRawValue()
        return anyGenerator { Self(rawValue: nextIndex++) }
    }
    static func firstRawValue() -> Int { return 0 }
}

现在,无论何时创建枚举(只要它具有Int原始值),都可以通过遵守以下协议使其可枚举:

enum Rank: Int, EnumerableEnum {
    case Ace, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King
}
// ...
for rank in Rank.enumerate() { ... }

如果您的枚举值不是以0(默认)开头,请重写此firstRawValue方法:

enum DeckColor: Int, EnumerableEnum {
    case Red = 10, Blue, Black
    static func firstRawValue() -> Int { return Red.rawValue }
}
// ...
let colors = Array(DeckColor.enumerate())

最后西服类,包括更换simpleDescription更标准CustomStringConvertible协议,将是这样的:

enum Suit: Int, CustomStringConvertible, EnumerableEnum {
    case Spades, Hearts, Diamonds, Clubs
    var description: String {
        switch self {
        case .Spades:   return "Spades"
        case .Hearts:   return "Hearts"
        case .Diamonds: return "Diamonds"
        case .Clubs:    return "Clubs"
        }
    }
}
// ...
for suit in Suit.enumerate() {
    print(suit.description)
}

Swift 3语法:

protocol EnumerableEnum {
    init?(rawValue: Int)
    static func firstRawValue() -> Int
}

extension EnumerableEnum {
    static func enumerate() -> AnyIterator<Self> {
        var nextIndex = firstRawValue()

        let iterator: AnyIterator<Self> = AnyIterator {
            defer { nextIndex = nextIndex + 1 }
            return Self(rawValue: nextIndex)
        }

        return iterator
    }

    static func firstRawValue() -> Int {
        return 0
    }
}

nextIndex ++将被迅速删除3。作为替换的建议-var nextIndex = firstRawValue()return anyGenerator {Self(rawValue:nextIndex ++)}
Avi

弄清楚了。defer {nextIndex + = 1} return AnyGenerator {Self(rawValue:nextIndex)}
Avi

12

更新到Swift 2.2 +

func iterateEnum<T: Hashable>(_: T.Type) -> AnyGenerator<T> {
    var i = 0
    return AnyGenerator {
        let next = withUnsafePointer(&i) {
            UnsafePointer<T>($0).memory
        }
        if next.hashValue == i {
            i += 1
            return next
        } else {
            return nil
        }
    }
}

它已将代码更新为Swift 2.2形式@Kametrixom的答案

对于Swift 3.0+(非常感谢@Philip

func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
    var i = 0
    return AnyIterator {
        let next = withUnsafePointer(&i) {
            UnsafePointer<T>($0).pointee
        }
        if next.hashValue == i {
            i += 1
            return next
        } else {
            return nil
        }
    }
}

@silvansky您能解释一下什么意思吗?
ale_stro

糟糕,抱歉,我重新检查了一下,发现了一个操场上的bug:在实际项目中,此代码可以按预期工作,谢谢!=)
silvansky '16

1
很好的解决方案!同样,在Swift 3上所做的改动也很小(“ AnyGenerator”重命名为“ AnyIterator”,“。memory”重命名为“ .pointee”)。
菲利普(Philip)

9

Swift 5解决方案:

enum Suit: String, CaseIterable {
    case spades = "♠"
    case hearts = "♥"
    case diamonds = "♦"
    case clubs = "♣"
}

// access cases like this:

for suitKey in Suit.allCases {
    print(suitKey)
}

7

我发现自己在.allValues整个代码中做很多事情。我终于找到了一种简单地遵循Iteratable协议并拥有rawValues()方法的方法。

protocol Iteratable {}
extension RawRepresentable where Self: RawRepresentable {

    static func iterateEnum<T: Hashable>(_: T.Type) -> AnyIterator<T> {
        var i = 0
        return AnyIterator {
            let next = withUnsafePointer(to: &i) {
                $0.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee }
            }
            if next.hashValue != i { return nil }
            i += 1
            return next
        }
    }
}

extension Iteratable where Self: RawRepresentable, Self: Hashable {
    static func hashValues() -> AnyIterator<Self> {
        return iterateEnum(self)
    }

    static func rawValues() -> [Self.RawValue] {
        return hashValues().map({$0.rawValue})
    }
}


// Example
enum Grocery: String, Iteratable {
    case Kroger = "kroger"
    case HEB = "h.e.b."
    case Randalls = "randalls"
}

let groceryHashes = Grocery.hashValues() // AnyIterator<Grocery>
let groceryRawValues = Grocery.rawValues() // ["kroger", "h.e.b.", "randalls"]

7

带有Swift 4.2的Xcode 10

enum Filter: String, CaseIterable {

    case salary = "Salary"
    case experience = "Experience"
    case technology = "Technology"
    case unutilized = "Unutilized"
    case unutilizedHV = "Unutilized High Value"

    static let allValues = Filter.allCases.map { $0.rawValue }
}

称它为

print(Filter.allValues)

印刷品:

[“工资”,“经验”,“技术”,“未利用”,“未利用的高价值”]


旧版本

对于enum代表Int

enum Filter: Int {
    case salary
    case experience
    case technology
    case unutilized
    case unutilizedHV

    static let allRawValues = salary.rawValue...unutilizedHV.rawValue  // First to last case
    static let allValues = allRawValues.map { Filter(rawValue: $0)!.rawValue }
}

这样称呼它:

print(Filter.allValues)

印刷品:

[0,1,2,3,4]


对于enum代表String

enum Filter: Int {
    case salary
    case experience
    case technology
    case unutilized
    case unutilizedHV

    static let allRawValues = salary.rawValue...unutilizedHV.rawValue  // First to last case
    static let allValues = allRawValues.map { Filter(rawValue: $0)!.description }
}

extension Filter: CustomStringConvertible {
    var description: String {
        switch self {
        case .salary: return "Salary"
        case .experience: return "Experience"
        case .technology: return "Technology"
        case .unutilized: return "Unutilized"
        case .unutilizedHV: return "Unutilized High Value"
        }
    }
}

称它为

print(Filter.allValues)

印刷品:

[“工资”,“经验”,“技术”,“未利用”,“未利用的高价值”]


7

编辑: 快速发展提案 SE-0194枚举案例的派生集合提出了一个针对该问题的有针对性的解决方案。我们在Swift 4.2及更高版本中看到了它。该提案还指出了一些变通办法,这些变通办法与此处已经提到的变通办法相似,但是仍然有可能引起人们的兴趣。

为了完整起见,我还将保留原始帖子。


这是基于@Peymmankh的答案的另一种方法,适用于Swift 3

public protocol EnumCollection: Hashable {}

extension EnumCollection {

public static func allValues() -> [Self] {
    typealias S = Self

    let retVal = AnySequence { () -> AnyIterator<S> in
        var raw = 0
        return AnyIterator {
            let current = withUnsafePointer(to: &raw) {
                 $0.withMemoryRebound(to: S.self, capacity: 1) { $0.pointee }
            }
            guard current.hashValue == raw else { return nil }
            raw += 1
            return current
        }
    }

    return [S](retVal)
}

5
enum Rank: Int {
    ...
    static let ranks = (Rank.Ace.rawValue ... Rank.King.rawValue).map{Rank(rawValue: $0)! }

}
enum Suit {
    ...
    static let suits = [Spades, Hearts, Diamonds, Clubs]
}

struct Card {
    ...
    static func fullDesk() -> [Card] {
        var desk: [Card] = []
        for suit in Suit.suits {
            for rank in Rank.ranks {
                desk.append(Card(rank: rank,suit: suit))
            }
        }
        return desk
    }
}

这个怎么样?


谢谢,它按我的需要工作 但是在map-closure中是否有机会不是按索引而是按名称来获取值?
米哈伊尔(Mikhail)

4

您可以尝试这样枚举

enum Planet: String {
    case Mercury
    case Venus
    case Earth
    case Mars

    static var enumerate: [Planet] {
        var a: [Planet] = []
        switch Planet.Mercury {
            case .Mercury: a.append(.Mercury); fallthrough
            case .Venus: a.append(.Venus); fallthrough
            case .Earth: a.append(.Earth); fallthrough
            case .Mars: a.append(.Mars)
        }
    return a
    }
}

Planet.enumerate // [Mercury, Venus, Earth, Mars]

1
那是很多无用的代码!这相当于static var enumerate = [Mercury, Venus, Earth, Mars],使之成为欠佳的回答相比,大多数投答案stackoverflow.com/a/24137319/1033581
心教堂

@Cœur这个答案的重要好处是使用编译器可以确保您不会错过任何情况。
dchakarov

@Cœur的问题相同,它允许您犯用户错误,即,如果您编写return [Mercury, Venus, Mars]而不是return [Mercury, Venus, Earth, Mars]
dchakarov

@dchakarov我决定后的改善作为一个答案,为清楚:stackoverflow.com/a/50409525/1033581
心教堂

@Cœur如果在您的新答案中用此替换return语句,return [.spades, .hearts, .clubs]编译器不会说什么,然后当您尝试在代码中使用它时,您会得到[TestApp.Suit.spades, TestApp.Suit.hearts, TestApp.Suit.clubs]-这就是我的观点-如果您要处理的是枚举,并且您必须不时添加或删除案例,您的解决方案很容易遗漏错误,而当前答案虽然不够简洁,但更安全。
dchakarov

4

在Swift 3中,当基础枚举具有时rawValue,您可以实现Strideable协议。优点是不会像其他建议中那样创建值数组,并且标准Swift“ for in”循环也可以工作,这使语法很不错。

// "Int" to get rawValue, and Strideable so we can iterate
enum MyColorEnum: Int, Strideable {
    case Red
    case Green
    case Blue
    case Black

    // required by Strideable
    typealias Stride = Int

    func advanced(by n:Stride) -> MyColorEnum {
        var next = self.rawValue + n
        if next > MyColorEnum.Black.rawValue {
            next = MyColorEnum.Black.rawValue
        }
        return MyColorEnum(rawValue: next)!
    }

    func distance(to other: MyColorEnum) -> Int {
        return other.rawValue - self.rawValue
    }

    // just for printing
    func simpleDescription() -> String {
        switch self {
        case .Red: return "Red"
        case .Green: return "Green"
        case .Blue: return "Blue"
        case .Black: return "Black"
        }
    }
}

// this is how you use it:
for i in MyColorEnum.Red ... MyColorEnum.Black {
    print("ENUM: \(i)")
}

啊,正是我想要取代ForwardIndexType的东西。现在,我的迭代在使用站点上看起来不错……只是正确的Swifty方式。
安德鲁·邓肯

4

该解决方案在可读性和可维护性之间取得了适当的平衡。

struct Card {

    // ...

    static func deck() -> Card[] {
        var deck = Card[]()
        for rank in Rank.Ace.toRaw()...Rank.King.toRaw() {
            for suit in [Suit.Spades, .Hearts, .Clubs, .Diamonds] {
                let card = Card(rank: Rank.fromRaw(rank)!, suit: suit)
                deck.append(card)
            }
        }
    return deck
    }
}

let deck = Card.deck()

我认为这是最好的解决方案。当我看到快速代码时,可读性通常不比objc好。但是,如果程序员更加关注其代码的读者,则可能是这样。他们的未来的自己,例如:)
维勒姆·库尔兹

4

抱歉,我的答案特定于我在需要做的事情中使用这篇文章的方式。对于那些偶然发现此问题的人,寻找一种在枚举中找到案例的方法,这是解决问题的方法(Swift 2中的新增功能):

编辑:小写的camelCase现在是Swift 3枚举值的标准

// From apple docs: If the raw-value type is specified as String and you don’t assign values to the cases explicitly, each unassigned case is implicitly assigned a string with the same text as the name of that case.

enum Theme: String
    {
    case white, blue, green, lavender, grey
    }

func loadTheme(theme: String)
    {
    // this checks the string against the raw value of each enum case (note that the check could result in a nil value, since it's an optional, which is why we introduce the if/let block
    if let testTheme = Theme(rawValue: theme)
        {
        // testTheme is guaranteed to have an enum value at this point
        self.someOtherFunction(testTheme)
        }
    }

对于那些想枚举枚举的人,此页面上给出的答案是正确的,该答案包括一个包含所有枚举值数组的静态var / let。Apple最新的tvOS示例代码包含此完全相同的技术。

话虽这么说,他们应该在语言中建立更方便的机制(Apple,您在听吗?)!


3

实验是:实验

向Card添加一种方法,以创建一副完整的纸牌,每个等级和花色组合都包含一张纸牌。

因此,除了添加方法(没有使用尚未讲授的内容)外,没有修改或增强给定的代码,我想出了以下解决方案:

struct Card {
    var rank: Rank
    var suit: Suit

    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }

    func createDeck() -> [Card] {
        var deck: [Card] = []
        for rank in Rank.Ace.rawValue...Rank.King.rawValue {
            for suit in Suit.Spades.rawValue...Suit.Clubs.rawValue {
                let card = Card(rank: Rank(rawValue: rank)!, suit: Suit(rawValue: suit)!)
                //println(card.simpleDescription())
                deck += [card]
            }
        }
        return deck
    }
}
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()
let deck = threeOfSpades.createDeck()

3

这是我用来迭代enum和从一个提供多个值类型的方法enum

enum IterateEnum: Int {
    case Zero
    case One
    case Two
    case Three
    case Four
    case Five
    case Six
    case Seven

    //tuple allows multiple values to be derived from the enum case, and
    //since it is using a switch with no default, if a new case is added,
    //a compiler error will be returned if it doesn't have a value tuple set
    var value: (french: String, spanish: String, japanese: String) {
        switch self {
        case .Zero: return (french: "zéro", spanish: "cero", japanese: "nuru")
        case .One: return (french: "un", spanish: "uno", japanese: "ichi")
        case .Two: return (french: "deux", spanish: "dos", japanese: "ni")
        case .Three: return (french: "trois", spanish: "tres", japanese: "san")
        case .Four: return (french: "quatre", spanish: "cuatro", japanese: "shi")
        case .Five: return (french: "cinq", spanish: "cinco", japanese: "go")
        case .Six: return (french: "six", spanish: "seis", japanese: "roku")
        case .Seven: return (french: "sept", spanish: "siete", japanese: "shichi")
        }
    }

    //Used to iterate enum or otherwise access enum case by index order.
    //Iterate by looping until it returns nil
    static func item(index: Int) -> IterateEnum? {
        return IterateEnum.init(rawValue: index)
    }

    static func numberFromSpanish(number: String) -> IterateEnum? {
        return findItem { $0.value.spanish == number }
    }

    //use block to test value property to retrieve the enum case        
    static func findItem(predicate: ((_: IterateEnum) -> Bool)) -> IterateEnum? {

        var enumIndex: Int = -1
        var enumCase: IterateEnum?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = IterateEnum.item(index: enumIndex)

            if let eCase = enumCase {

                if predicate(eCase) {
                    return eCase
                }
            }
        } while enumCase != nil
        return nil
    }
}

var enumIndex: Int = -1
var enumCase: IterateEnum?

// Iterate until item returns nil
repeat {
    enumIndex += 1
    enumCase = IterateEnum.item(index: enumIndex)
    if let eCase = enumCase {
        print("The number \(eCase) in french: \(eCase.value.french), spanish: \(eCase.value.spanish), japanese: \(eCase.value.japanese)")
    }
} while enumCase != nil

print("Total of \(enumIndex) cases")

let number = IterateEnum.numberFromSpanish(number: "siete")

print("siete in japanese: \((number?.value.japanese ?? "Unknown"))")

这是输出:

在法国的数字零:零,西班牙语:CERO,日本:努鲁
头号法语:联合国,西班牙语:UNO,日本:ICHI
二号法文:德塞夫勒,西班牙语:DOS,日本:NI
在法国排名第三:trois,西班牙语:tres,日语:san
法语中的第四位:quatre,西班牙语:cuatro,日语:shi
法语中的第五位:cinq,西班牙语:cinco,日语:go
法语中的第六位:六个,西班牙语: seis,日语:roku
法语中的第七:sept,西班牙语:siete,日语:shichi

共有8例

siete日语:shichi


更新

我最近创建了一个协议来处理枚举。该协议需要一个具有Int原始值的枚举:

protocol EnumIteration {

    //Used to iterate enum or otherwise access enum case by index order. Iterate by looping until it returns nil

    static func item(index:Int) -> Self?
    static func iterate(item:((index:Int, enumCase:Self)->()), completion:(()->())?) {
    static func findItem(predicate:((enumCase:Self)->Bool)) -> Self?
    static func count() -> Int
}

extension EnumIteration where Self: RawRepresentable, Self.RawValue == Int {

    //Used to iterate enum or otherwise access enum case by index order. Iterate by looping until it returns nil
    static func item(index:Int) -> Self? {
        return Self.init(rawValue: index)
    }

    static func iterate(item:((index:Int, enumCase:Self)->()), completion:(()->())?) {

        var enumIndex:Int = -1
        var enumCase:Self?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = Self.item(enumIndex)

            if let eCase = enumCase {
                item(index: enumIndex, enumCase: eCase)
            }
        } while enumCase != nil
        completion?()
    }

    static func findItem(predicate:((enumCase:Self)->Bool)) -> Self? {

        var enumIndex:Int = -1
        var enumCase:Self?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = Self.item(enumIndex)

            if let eCase = enumCase {

                if predicate(enumCase:eCase) {
                    return eCase
                }
            }
        } while enumCase != nil
        return nil
    }

    static func count() -> Int {
        var enumIndex:Int = -1
        var enumCase:Self?

        //Iterate until item returns nil
        repeat {
            enumIndex += 1
            enumCase = Self.item(enumIndex)
        } while enumCase != nil

        //last enumIndex (when enumCase == nil) is equal to the enum count
        return enumIndex
    }
}

2

这似乎是一种hack,但是如果您使用原始值,则可以执行以下操作

enum Suit: Int {  
    case Spades = 0, Hearts, Diamonds, Clubs  
 ...  
}  

var suitIndex = 0  
while var suit = Suit.fromRaw(suitIndex++) {  
   ...  
}  

2

Swift 2.0我的建议是在这里处理:

我已经将原始类型添​​加到 Suit enum

enum Suit: Int {

然后:

struct Card {
    var rank: Rank
    var suit: Suit


    func fullDeck()-> [Card] {

        var deck = [Card]()

        for i in Rank.Ace.rawValue...Rank.King.rawValue {

            for j in Suit.Spades.rawValue...Suit.Clubs.rawValue {

                deck.append(Card(rank:Rank(rawValue: i)! , suit: Suit(rawValue: j)!))
            }
        }

        return deck
    }
}

2

与@Kametrixom一样在这里回答我相信返回数组比返回AnySequence更好,因为您可以访问Array的所有功能,例如count等。

这是重写:

public protocol EnumCollection : Hashable {}
extension EnumCollection {
    public static func allValues() -> [Self] {
        typealias S = Self
        let retVal = AnySequence { () -> AnyGenerator<S> in
            var raw = 0
            return AnyGenerator {
                let current : Self = withUnsafePointer(&raw) { UnsafePointer($0).memory }
                guard current.hashValue == raw else { return nil }
                raw += 1
                return current
            }
        }

        return [S](retVal)
    }
}

2

另一个解决方案:

enum Suit: String {
    case spades = "♠"
    case hearts = "♥"
    case diamonds = "♦"
    case clubs = "♣"

    static var count: Int {
        return 4   
    }

    init(index: Int) {
        switch index {
            case 0: self = .spades
            case 1: self = .hearts
            case 2: self = .diamonds
            default: self = .clubs
        }
    }
}

for i in 0..<Suit.count {
    print(Suit(index: i).rawValue)
}

2

这是来自Swift 2.0的相当古老的文章。现在,这里有一些更好的解决方案使用了Swift 3.0的新功能: 在Swift 3.0中遍历枚举

在这个问题上,有一个解决方案使用了一项新功能(在我撰写此编辑时尚未发布)Swift 4.2: 如何获得Swift枚举的计数?


这个线程中有很多好的解决方案,而其他一些却非常复杂。我喜欢尽可能简化。这是一个可能会或可能无法满足不同需求的解决方案,但我认为它在大多数情况下都能很好地工作:

enum Number: String {
    case One
    case Two
    case Three
    case Four
    case EndIndex

    func nextCase () -> Number
    {
        switch self {
        case .One:
            return .Two
        case .Two:
            return .Three
        case .Three:
            return .Four
        case .Four:
            return .EndIndex

        /* 
        Add all additional cases above
        */
        case .EndIndex:
            return .EndIndex
        }
    }

    static var allValues: [String] {
        var array: [String] = Array()
        var number = Number.One

        while number != Number.EndIndex {
            array.append(number.rawValue)
            number = number.nextCase()
        }
        return array
    }
}

要迭代:

for item in Number.allValues {
    print("number is: \(item)")
}

1
这感觉像是针对您创建的单个枚举的大量工作-在这种情况下,我不确定返回[Number.One.rawValue,Number.Two.rawValue,...]不会更干净。
斯科特·奥斯汀

这是来自Swift 2.0的相当古老的文章。现在,这里有一些使用swift 3.0的较新功能的更好的解决方案:stackoverflow.com/questions/41352594/… 关于这个问题,有一个解决方案使用了(我编写此编辑时尚未发布的)新功能。 )雨燕4.2:stackoverflow.com/questions/27094878/...
修道院杰克逊

2

枚举有toRaw()fromRaw()方法。因此,如果原始值为an Int,则可以从头到尾进行迭代enum

enum Suit: Int {
    case Spades = 1
    case Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
        switch self {
        case .Spades:
            return "spades"
        case .Hearts:
            return "hearts"
        case .Diamonds:
            return "diamonds"
        case .Clubs:
            return "clubs"
        }
    }
}

for i in Suit.Spades.toRaw()...Suit.Clubs.toRaw() {
    if let covertedSuit = Suit.fromRaw(i) {
        let description = covertedSuit.simpleDescription()
    }
}

一个陷阱是您需要在运行该simpleDescription方法之前测试可选值,因此我们convertedSuit首先将其设置为值,然后将一个常量设置为convertedSuit.simpleDescription()


2
最初的问题是关于不是Int的String类型的枚举
cynistersix 2014年

2

这是我建议的方法。这并不完全令人满意(我对Swift和OOP还是很陌生!),但也许有人可以对其进行完善。想法是让每个枚举提供其自身的范围信息.first.last属性。它向每个枚举只添加了两行代码:仍然有些硬编码,但至少没有重复整个代码集。它确实需要SuitRank枚举一样将枚举修改为一个Int ,而不是未键入。

这不是在echo整个解决方案,而是.在case语句后的某个位置(Suit枚举类似)添加到枚举的代码:

var first: Int { return Ace.toRaw() }
var last: Int { return King.toRaw() }

和我用来将牌组构建为String数组的循环。(问题定义未说明平台的结构。)

func createDeck() -> [String] {
    var deck: [String] = []
    var card: String
    for r in Rank.Ace.first...Rank.Ace.last {
        for s in Suit.Hearts.first...Suit.Hearts.last {
            card = Rank.simpleDescription( Rank.fromRaw(r)!)() + " of " + Suit.simpleDescription( Suit.fromRaw(s)!)()
           deck.append( card)
       }
    }
    return deck
}

这是不令人满意的,因为属性是与元素而不是枚举关联的。但这确实增加了“ for”循环的清晰度。我想说的Rank.first不是Rank.Ace.first。它可以(与任何元素一起使用),但是很难看。有人可以展示如何将其提升到枚举级别吗?

为了使其工作,我createDeck从Card结构中提出了该方法。我无法弄清楚如何从该结构中返回[String]数组,无论如何放置这种方法似乎是一个不好的地方。


2

我使用计算属性完成此操作,该属性返回所有值的数组(由于这篇文章http://natecook.com/blog/2014/10/loopy-random-enum-ideas/)。但是,它也使用int原始值,但是我不需要在单独的属性中重复所有枚举成员。

UPDATE Xcode 6.1更改了如何使用以下方法获取枚举成员的方式rawValue,因此我修复了列表。还修复了先犯错误的小错误rawValue

enum ValidSuits: Int {
    case Clubs = 0, Spades, Hearts, Diamonds
    func description() -> String {
        switch self {
        case .Clubs:
            return "♣︎"
        case .Spades:
            return "♠︎"
        case .Diamonds:
            return "♦︎"
        case .Hearts:
            return "♥︎"
        }
    }

    static var allSuits: [ValidSuits] {
        return Array(
            SequenceOf {
                () -> GeneratorOf<ValidSuits> in
                var i=0
                return GeneratorOf<ValidSuits> {
                    return ValidSuits(rawValue: i++)
                }
            }
        )
    }
}

1
enum Rank: Int
{
    case Ace = 0
    case Two, Three, Four, Five, Six, Seve, Eight, Nine, Ten
    case Jack, Queen, King
    case Count
}

enum Suit : Int
{
    case Spades = 0
    case Hearts, Diamonds, Clubs
    case Count
}

struct Card
{
    var rank:Rank
    var suit:Suit
}

class Test
{
    func makeDeck() -> Card[]
    {
        let suitsCount:Int = Suit.Count.toRaw()
        let rankCount:Int = Rank.Count.toRaw()
        let repeatedCard:Card = Card(rank:Rank.Ace, suit:Suit.Spades)
        let deck:Card[] = Card[](count:suitsCount*rankCount, repeatedValue:repeatedCard)

        for i:Int in 0..rankCount
        {
            for j:Int in 0..suitsCount
            {
                deck[i*suitsCount+j] = Card(rank: Rank.fromRaw(i)!, suit: Suit.fromRaw(j)!)
            }
        }
        return deck
    }
}

根据里克的答案:这快了5倍


添加Count案例将破坏详尽的switch实现和CaseIterable一致性。
心教堂
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.