如何获得Swift枚举的计数?

How do I get the count of a Swift enum?

如何确定swift枚举中的事例数?

(我希望避免手动枚举所有值,或者尽可能使用旧的"枚举计数技巧"。)


我有一篇关于这个的更详细的博客文章,但是只要你的枚举的原始类型是一个整数,你就可以这样添加一个计数:

1
2
3
4
5
6
7
8
9
10
enum Reindeer: Int {
    case Dasher, Dancer, Prancer, Vixen, Comet, Cupid, Donner, Blitzen
    case Rudolph

    static let count: Int = {
        var max: Int = 0
        while let _ = Reindeer(rawValue: max) { max += 1 }
        return max
    }()
}


从Swift 4.2(Xcode 10)起,您可以申报符合CaseIterable协议,这对所有人都有效。没有关联值的枚举:

1
2
3
4
5
6
enum Stuff: CaseIterable {
    case first
    case second
    case third
    case forth
}

现在只需使用

1
print(Stuff.allCases.count) // 4

有关详细信息,请参阅

  • SE-0194派生的枚举事例集合


XCODE 10更新

在枚举中采用CaseIterable协议,它提供一个静态allCases属性,其中包含所有枚举案例作为Collection属性。只需使用它的count属性就可以知道枚举有多少个事例。

以马丁的回答为例(并将他的回答比我的更高)

警告:下面的方法似乎不再有效。

我不知道任何计算枚举事例数的通用方法。不过,我注意到枚举案例的hashValue属性是递增的,从零开始,其顺序由声明案例的顺序决定。因此,最后一个枚举加上一个的散列值对应于事例数。

例如,对于此枚举:

1
2
3
4
5
6
7
8
enum Test {
    case ONE
    case TWO
    case THREE
    case FOUR

    static var count: Int { return Test.FOUR.hashValue + 1}
}

count返回4。

我不能说这是一条规则,还是它将来会改变,所以请自担风险使用:)


我定义了一个可重用的协议,它根据nate cook发布的方法自动执行案例计数。

1
2
3
4
5
6
7
8
9
10
11
12
13
protocol CaseCountable {
    static var caseCount: Int { get }
}

extension CaseCountable where Self: RawRepresentable, Self.RawValue == Int {
    internal static var caseCount: Int {
        var count = 0
        while let _ = Self(rawValue: count) {
            count += 1
        }
        return count
    }
}

然后我可以重用这个协议,例如:

1
2
3
4
5
enum Planet : Int, CaseCountable {
    case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune
}
//..
print(Planet.caseCount)


创建静态AllValues数组,如此答案所示

1
2
3
4
5
6
7
8
9
enum ProductCategory : String {
     case Washers ="washers", Dryers ="dryers", Toasters ="toasters"

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

...

let count = ProductCategory.allValues.count

当您想要枚举值,并且适用于所有枚举类型时,这也很有用。


如果实现没有任何反对使用整数枚举的内容,可以添加一个名为count的额外成员值来表示枚举中的成员数-请参见下面的示例:

1
2
3
4
5
enum TableViewSections : Int {
  case Watchlist
  case AddButton
  case Count
}

现在,您可以通过调用TableViewSections.Count.rawValue来获取枚举中的成员数,对于上面的示例,它将返回2。

当您在switch语句中处理枚举时,请确保在遇到您不期望的count成员时引发断言失败:

1
2
3
4
5
6
7
8
9
10
11
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  let currentSection: TableViewSections = TableViewSections.init(rawValue:section)!
  switch(currentSection) {
  case .Watchlist:
    return watchlist.count
  case .AddButton:
    return 1
  case .Count:
    assert(false,"Invalid table view section!")
  }
}


这种函数可以返回枚举的计数。

斯威夫特2:

1
2
3
4
5
6
7
func enumCount<T: Hashable>(_: T.Type) -> Int {
    var i = 1
    while (withUnsafePointer(&i) { UnsafePointer<T>($0).memory }).hashValue != 0 {
        i += 1
    }
    return i
}

斯威夫特3:

1
2
3
4
5
6
7
8
9
func enumCount<T: Hashable>(_: T.Type) -> Int {
   var i = 1
   while (withUnsafePointer(to: &i, {
      return $0.withMemoryRebound(to: T.self, capacity: 1, { return $0.pointee })
   }).hashValue != 0) {
      i += 1
   }
      return i
   }


大家好,单元测试怎么样?

1
2
3
4
5
6
7
func testEnumCountIsEqualToNumberOfItemsInEnum() {

    var max: Int = 0
    while let _ = Test(rawValue: max) { max += 1 }

    XCTAssert(max == Test.count)
}

这与安东尼奥的解决方案相结合:

1
2
3
4
5
6
7
8
9
enum Test {

    case one
    case two
    case three
    case four

    static var count: Int { return Test.four.hashValue + 1}
}

在主代码中,如果有人添加了一个枚举案例five,并且没有更新count的实现,那么您将得到一个失败的测试。


String Enum with Index

1
2
3
4
5
6
7
8
9
10
11
12
13
enum eEventTabType : String {
    case Search     ="SEARCH"
    case Inbox      ="INBOX"
    case Accepted   ="ACCEPTED"
    case Saved      ="SAVED"
    case Declined   ="DECLINED"
    case Organized  ="ORGANIZED"

    static let allValues = [Search, Inbox, Accepted, Saved, Declined, Organized]
    var index : Int {
       return eEventTabType.allValues.indexOf(self)!
    }
}

计数:eEventTabType.allValues.count

索引:objeEventTabType.index

享受:


此功能依赖于2个未记录的电流(Swift 1.1)enum行为:

  • enum的内存布局只是case的索引。如果病例数从2到256,则为UInt8
  • 如果enum是由无效的case索引进行位转换,则其hashValue0

因此,请自行承担风险:)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func enumCaseCount<T:Hashable>(t:T.Type) -> Int {
    switch sizeof(t) {
    case 0:
        return 1
    case 1:
        for i in 2..<256 {
            if unsafeBitCast(UInt8(i), t).hashValue == 0 {
                return i
            }
        }
        return 256
    case 2:
        for i in 257..<65536 {
            if unsafeBitCast(UInt16(i), t).hashValue == 0 {
                return i
            }
        }
        return 65536
    default:
        fatalError("too many")
    }
}

用途:

1
2
3
4
5
6
enum Foo:String {
    case C000 ="foo"
    case C001 ="bar"
    case C002 ="baz"
}
enumCaseCount(Foo) // -> 3


我编写了一个简单的扩展,它给出了原始值为整数的所有枚举的count属性:

1
2
3
4
5
6
7
8
9
extension RawRepresentable where RawValue: IntegerType {
    static var count: Int {
        var i: RawValue = 0
        while let _ = Self(rawValue: i) {
            i = i.successor()
        }
        return Int(i.toIntMax())
    }
}

不幸的是,它将count属性提供给OptionSetType,因为它无法正常工作,所以这里有另一个版本,它要求您要计算的任何枚举明确符合CaseCountable协议:

1
2
3
4
5
6
7
8
9
10
protocol CaseCountable: RawRepresentable {}
extension CaseCountable where RawValue: IntegerType {
    static var count: Int {
        var i: RawValue = 0
        while let _ = Self(rawValue: i) {
            i = i.successor()
        }
        return Int(i.toIntMax())
    }
}

它与TomPelaia发布的方法非常相似,但适用于所有整数类型。


当然,它不是动态的,但对于许多用途,可以通过将静态变量添加到枚举中来获得。

static var count: Int{ return 7 }

然后作为EnumName.count使用


1
2
3
4
5
6
7
8
9
enum EnumNameType: Int {
    case first
    case second
    case third

    static var count: Int { return EnumNameType.third.rawValue + 1 }
}

print(EnumNameType.count) //3

1
2
3
4
5
6
7
8
enum EnumNameType: Int {
    case first
    case second
    case third
    case count
}

print(EnumNameType.count.rawValue) //3

*在Swift 4.2(Xcode 10)上,可以使用:

1
2
3
4
5
6
7
enum EnumNameType: CaseIterable {
    case first
    case second
    case third
}

print(EnumNameType.allCases.count) //3

对于我的用例,在一个代码库中,多个人可以向一个枚举添加键,并且这些情况在all keys属性中都应该是可用的,对枚举中的键进行验证是很重要的。这是为了避免有人忘记将他们的密钥添加到"所有密钥"列表中。将all keys数组的计数(首先创建为一个集合以避免重复)与枚举中的键数相匹配,确保它们都存在。

上面的一些答案显示了在Swift2中实现这一点的方法,但在Swift3中没有任何一种方法。以下是Swift 3格式的版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static func enumCount<T: Hashable>(_ t: T.Type) -> Int {
    var i = 1
    while (withUnsafePointer(to: &i) {
      $0.withMemoryRebound(to:t.self, capacity:1) { $0.pointee.hashValue != 0 }
    }) {
      i += 1
    }
    return i
}

static var allKeys: [YourEnumTypeHere] {
    var enumSize = enumCount(YourEnumTypeHere.self)

    let keys: Set<YourEnumTypeHere> = [.all, .your, .cases, .here]
    guard keys.count == enumSize else {
       fatalError("Missmatch between allKeys(\(keys.count)) and actual keys(\(enumSize)) in enum.")
    }
    return Array(keys)
}

根据您的用例,您可能只想在开发中运行测试,以避免在每个请求上使用allkeys的开销。


你为什么把事情搞得这么复杂?int枚举的最简单计数器是添加:

case Count

最后。还有…维奥拉-现在你数到了-快速而简单


使用Int型枚举的Swift 3版本:

1
2
3
4
5
6
7
8
protocol CaseCountable: RawRepresentable {}
extension CaseCountable where RawValue == Int {
    static var count: RawValue {
        var i: RawValue = 0
        while let _ = Self(rawValue: i) { i += 1 }
        return i
    }
}

学分:根据BZZ和Nate Cook的答案。

不支持泛型IntegerType(在Swift 3中,重命名为Integer),因为它是一个严重分散的泛型类型,缺少很多函数。Swift 3不再提供successor

请注意,代码指挥官对nate cooks答案的评论仍然有效:

While nice because you don't need to hardcode a value, this will
instantiate every enum value each time it is called. That is O(n)
instead of O(1).

据我所知,当前在将此用作协议扩展时没有解决方法(并且没有像nate cook那样在每个枚举中实现),因为泛型类型中不支持静态存储属性。

无论如何,对于小的枚举来说,这应该是没有问题的。一个典型的使用案例是Zorayr已经提到的UITableViewssection.count


扩展Matthieu Riegler的答案,这是一个针对Swift 3的解决方案,不需要使用泛型,可以使用带EnumType.elementsCount的枚举类型轻松调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
extension RawRepresentable where Self: Hashable {

    // Returns the number of elements in a RawRepresentable data structure
    static var elementsCount: Int {
        var i = 1
        while (withUnsafePointer(to: &i, {
            return $0.withMemoryRebound(to: self, capacity: 1, { return
                   $0.pointee })
        }).hashValue != 0) {
            i += 1
        }
        return i
}

如果不想将代码作为最后一个枚举的基础,可以在枚举内创建此函数。

1
2
3
4
5
6
7
8
9
10
11
12
func getNumberOfItems() -> Int {
    var i:Int = 0
    var exit:Bool = false
    while !exit {
        if let menuIndex = MenuIndex(rawValue: i) {
            i++
        }else{
            exit = true
        }
    }
    return i
}

我通过创建一个协议(EnumitArray)和一个全局实用函数(EnumitArray)自己解决了这个问题,使向任何枚举添加"all"变量变得非常容易(使用swift 1.2)。"all"变量将包含枚举中所有元素的数组,因此可以使用all.count作为计数。

它只适用于使用int类型的原始值的枚举,但也许它可以为其他类型提供一些启发。

它还解决了我在上面和其他地方读到的"编号上的空白"和"重复时间过长"的问题。

其思想是将EnumitArray协议添加到枚举中,然后通过调用EnumitArray函数来定义一个"all"静态变量,并为其提供第一个元素(如果编号中存在间隙,则为最后一个元素)。

因为静态变量只初始化一次,所以遍历所有原始值的开销只会命中程序一次。

示例(无间隙):

1
2
3
4
5
enum Animals:Int, EnumIntArray
{
  case Cat=1, Dog, Rabbit, Chicken, Cow
  static var all = enumIntArray(Animals.Cat)
}

示例(带间隙):

1
2
3
4
5
6
enum Animals:Int, EnumIntArray
{
  case Cat    = 1,  Dog,
  case Rabbit = 10, Chicken, Cow
  static var all = enumIntArray(Animals.Cat, Animals.Cow)
}

实现它的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
protocol EnumIntArray
{
   init?(rawValue:Int)
   var rawValue:Int { get }
}

func enumIntArray<T:EnumIntArray>(firstValue:T, _ lastValue:T? = nil) -> [T]
{
   var result:[T] = []
   var rawValue   = firstValue.rawValue
   while true
   {
     if let enumValue = T(rawValue:rawValue++)
     { result.append(enumValue) }
     else if lastValue == nil                    
     { break }

     if lastValue != nil
     && rawValue  >  lastValue!.rawValue          
     { break }
   }
   return result  
}


或者,您只需在枚举外部定义_count,然后静态附加它:

1
2
3
4
5
6
7
8
9
10
11
let _count: Int = {
    var max: Int = 0
    while let _ = EnumName(rawValue: max) { max += 1 }
    return max
}()

enum EnumName: Int {
    case val0 = 0
    case val1
    static let count = _count
}

这样,无论您创建了多少枚举,它都只会创建一次。

(如果static这样做,请删除此答案)


以下方法来自corekit,与其他人提出的答案类似。这适用于Swift 4。

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
public protocol EnumCollection: Hashable {
    static func cases() -> AnySequence<Self>
    static var allValues: [Self] { get }
}

public extension EnumCollection {

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

    public static var allValues: [Self] {
        return Array(self.cases())
    }
}
1
2
3
enum Weekdays: String, EnumCollection {
    case sunday, monday, tuesday, wednesday, thursday, friday, saturday
}

然后你只需要打电话给Weekdays.allValues.count


它可以使用静态常量,该常量包含枚举的最后一个值和一个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
enum Color : Int {
    case  Red, Orange, Yellow, Green, Cyan, Blue, Purple

    static let count: Int = Color.Purple.rawValue + 1

    func toUIColor() -> UIColor{
        switch self {
            case .Red:
                return UIColor.redColor()
            case .Orange:
                return UIColor.orangeColor()
            case .Yellow:
                return UIColor.yellowColor()
            case .Green:
                return UIColor.greenColor()
            case .Cyan:
                return UIColor.cyanColor()
            case .Blue:
                return UIColor.blueColor()
            case .Purple:
                return UIColor.redColor()
        }
    }
}

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
struct HashableSequence<T: Hashable>: SequenceType {
    func generate() -> AnyGenerator<T> {
        var i = 0
        return AnyGenerator {
            let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
            if next.hashValue == i {
                i += 1
                return next
            }
            return nil
        }
    }
}

extension Hashable {
    static func enumCases() -> Array<Self> {
        return Array(HashableSequence())
    }

    static var enumCount: Int {
        return enumCases().enumCount
    }
}

enum E {
    case A
    case B
    case C
}

E.enumCases() // [A, B, C]
E.enumCount   //  3

但是要小心使用非枚举类型。一些解决方法可能是:

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
33
34
35
36
37
38
39
40
41
42
struct HashableSequence<T: Hashable>: SequenceType {
    func generate() -> AnyGenerator<T> {
        var i = 0
        return AnyGenerator {
            guard sizeof(T) == 1 else {
                return nil
            }
            let next = withUnsafePointer(&i) { UnsafePointer<T>($0).memory }
            if next.hashValue == i {
                i += 1
                return next
            }

            return nil
        }
    }
}

extension Hashable {
    static func enumCases() -> Array<Self> {
        return Array(HashableSequence())
    }

    static var enumCount: Int {
        return enumCases().count
    }
}

enum E {
    case A
    case B
    case C
}

Bool.enumCases()   // [false, true]
Bool.enumCount     // 2
String.enumCases() // []
String.enumCount   // 0
Int.enumCases()    // []
Int.enumCount      // 0
E.enumCases()      // [A, B, C]
E.enumCount        // 4

这是次要的,但我认为更好的O(1)解决方案是如下(仅当您的枚举是从x开始的Int等):

1
2
3
4
5
6
7
8
9
enum Test : Int {
    case ONE = 1
    case TWO
    case THREE
    case FOUR // if you later need to add additional enums add above COUNT so COUNT is always the last enum value
    case COUNT

    static var count: Int { return Test.COUNT.rawValue } // note if your enum starts at 0, some other number, etc. you'll need to add on to the raw value the differential
}

我仍然认为,当前选定的答案是所有枚举的最佳答案,除非您使用Int,否则我建议使用此解决方案。