如何获得 Swift 枚举的计数?

Posted

技术标签:

【中文标题】如何获得 Swift 枚举的计数?【英文标题】:How do I get the count of a Swift enum? 【发布时间】:2015-01-21 13:51:26 【问题描述】:

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

(我想避免使用manually enumerating through all the values,或者尽可能使用旧的“enum_count trick”。)

【问题讨论】:

【参考方案1】:

从 Swift 4.2 (Xcode 10) 开始,您可以声明 符合CaseIterable 协议,这适用于所有人 没有关联值的枚举:

enum Stuff: CaseIterable 
    case first
    case second
    case third
    case forth

现在可以简单地获得案例数

print(Stuff.allCases.count) // 4

有关详细信息,请参阅

SE-0194 Derived Collection of Enum Cases

【讨论】:

在最新版本的 swift 中,它的抛出错误“类型'DAFFlow'不符合协议'RawRepresentable'”。为什么强迫我遵循这个?有什么想法吗? @Satyam:什么是 DAFFlow? 对不起,我忘了提到“DAFFlow”是一个简单的枚举,它不从任何其他协议继承。 这是最好的解决方案,但只是为了清楚起见 - Apple 开发人员只有在 Xcode 10(以及因此 Swift 4.2)推出测试版后才能真正开始使用它(很可能在 2018 年 9 月 14 日左右) )。 @DaniSpringer:您可以在github.com/apple/swift-evolution/blob/master/proposals/… 找到血腥的细节。但由于编译器的自动类型推断,通常您不需要显式地使用该类型。【参考方案2】:

我有a blog post 对此进行了更详细的说明,但只要您的枚举的原始类型是整数,您就可以通过这种方式添加计数:

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
    ()

【讨论】:

虽然很好,因为您不需要对值进行硬编码,但这将在每次调用时实例化每个枚举值。那是 O(n) 而不是 O(1)。 :( 这是连续 Int 的一个很好的解决方案。我更喜欢稍作修改。将静态 count 属性转换为静态方法 countCases() 并将其分配给静态常量 caseCount,该常量是惰性的,可通过重复调用提高性能。 @ShamsAhmed:将计算的变量转换为静态变量。 如果你错过了枚举中的一些值怎么办?例如case A=1, B=3 ? 除了 enum 有一个 Int 原始值之外还有 2 个假设,但您忘了提及:带有 Int 原始值的 Swift 枚举不必从 0 开始(尽管这是默认行为) 并且它们的原始值可以是任意的,它们不必增加 1(即使这是默认行为)。【参考方案3】:

Xcode 10 更新

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

请参阅 Martin 的答案以获取示例(并支持他的答案而不是我的答案)


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

我不知道有任何通用方法来计算枚举案例的数量。但是,我注意到枚举案例的 hashValue 属性是递增的,从零开始,并且顺序由声明案例的顺序决定。因此,最后一个枚举的哈希加一对应于案例的数量。

以这个枚举为例:

enum Test 
    case ONE
    case TWO
    case THREE
    case FOUR

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

count 返回 4。

我不能说这是规则还是将来是否会改变,所以使用风险自负 :)

【讨论】:

靠无证特性生存,死于无证特性。我喜欢! 我们真的不应该依赖hashValues来处理这些事情;我们所知道的是它是一些随机的唯一值 - 将来可能会根据一些编译器实现细节很容易改变;但总的来说,缺乏内置计数功能令人不安。 如果您不介意明确设置case ONE = 0,则可以将hashValue 替换为rawValue 这里的问题是使用了未记录的 hashValue 属性,所以我的建议是使用 rawValue 的记录属性。 你已经硬编码了哪个常量是最高值的事实,最好更安全地使用像 static var count = 4 这样的东西,而不是将你的命运留给未来 Swift 实现的命运【参考方案4】:

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

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
    

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

enum Planet : Int, CaseCountable 
    case Mercury, Venus, Earth, Mars, Jupiter, Saturn, Uranus, Neptune

//..
print(Planet.caseCount)

【讨论】:

漂亮而优雅,应该是接受的答案恕我直言 也许最好将count++ 更改为count+=1,因为++ 符号将在Swift 3 中被删除 不能只用static var caseCount: Int get 吗?为什么需要static func 如果你错过了枚举中的一些值怎么办?例如case A=1, B=3 ? @Sasho,那就不行了。这要求您的案例从0 开始并且没有间隔。【参考方案5】:

创建静态 allValues 数组,如 answer 所示

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

     static let allValues = [Washers, Dryers, Toasters]


...

let count = ProductCategory.allValues.count

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

【讨论】:

虽然不像扩展解决方案那样优雅而且非常手动,但我相信这是最有用的,因为它提供的不仅仅是计数。它为您提供值的顺序和所有值的列表。 您也可以通过 static let count = allValues.count 将计数添加到枚举中。然后,如果需要,您可以将 allValues 设为私有。【参考方案6】:

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

enum TableViewSections : Int 
  case Watchlist
  case AddButton
  case Count

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

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

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!")
  

【讨论】:

我喜欢这个解决方案,因为它会在添加更多枚举值时自动更改计数。但是请记住,这仅在枚举的 rawValues 以 0 开头时才有效。 同意,有两个限制:必须是整数枚举并且必须从零开始并递增地继续。 我认为 Swift 更强大的枚举的全部意义在于我们不必使用在 Objective-C 中使用的相同技巧:/【参考方案7】:

这种函数能够返回枚举的计数。

Swift 2

func enumCount<T: Hashable>(_: T.Type) -> Int 
    var i = 1
    while (withUnsafePointer(&i)  UnsafePointer<T>($0).memory ).hashValue != 0 
        i += 1
    
    return i

Swift 3

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
   

【讨论】:

这不再适用于 Swift 3。试图找出正确的实现,但结果是空的 如果紧邻enum末尾的内存地址是Hashable的同一类型,这将是非常不愉快的调试。【参考方案8】:

带索引的字符串枚举

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

享受:)

【讨论】:

【参考方案9】:

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

func testEnumCountIsEqualToNumberOfItemsInEnum() 

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

    XCTAssert(max == Test.count)

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

enum Test 

    case one
    case two
    case three
    case four

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

在主代码中给你 O(1) 加上 你得到一个失败的测试 如果有人添加一个枚举案例 five 并且不更新的实现count.

【讨论】:

【参考方案10】:

此函数依赖于 2 个未记录的当前(Swift 1.1)enum 行为:

enum 的内存布局只是case 的索引。如果案例计数为 2 到 256,则为 UInt8。 如果 enum 是从 invalid 案例索引位转换的,则其 hashValue0

所以使用风险自负:)

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")
    

用法:

enum Foo:String 
    case C000 = "foo"
    case C001 = "bar"
    case C002 = "baz"

enumCaseCount(Foo) // -> 3

【讨论】:

在发布和临时应用程序将崩溃 这在模拟器中有效,但在真正的 64 位设备上无效。【参考方案11】:

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

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 协议的任何枚举,您想要计算的情况:

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())
    

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

【讨论】:

【参考方案12】:

当然,它不是动态的,但对于许多用途,您可以通过添加到 Enum 的静态 var 来解决

static var count: Int return 7

然后将其用作EnumName.count

【讨论】:

【参考方案13】:
enum EnumNameType: Int 
    case first
    case second
    case third

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


print(EnumNameType.count) //3

enum EnumNameType: Int 
    case first
    case second
    case third
    case count


print(EnumNameType.count.rawValue) //3

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

enum EnumNameType: CaseIterable 
    case first
    case second
    case third


print(EnumNameType.allCases.count) //3

【讨论】:

【参考方案14】:

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

上面的一些答案显示了在 Swift 2 中实现这一点的方法,但在 Swift 3 中没有一个可行的方法。这是 Swift 3 格式的版本:

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 的开销

【讨论】:

【参考方案15】:

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

case Count

最后。还有...中提琴 - 现在你有数了 - 快速而简单

【讨论】:

这 a) 添加了一个无关的枚举大小写,并且 b) 如果枚举的原始类型不是 Int 则将不起作用。 这实际上是一个不错的答案。然而,就像上面@Tom Pelaia 的回答一样,它要求原始值从0 开始,并且在序列中没有间隙。【参考方案16】:

如果您不想将代码基于最后一个枚举,您可以在枚举中创建此函数。

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

【讨论】:

【参考方案17】:

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

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),因为它是一个非常分散的泛型类型,缺少很多功能。 successor 不再适用于 Swift 3。

请注意,Code Commander 对 Nate Cooks 回答的评论仍然有效:

虽然很好,因为您不需要对值进行硬编码,但这将 每次调用它时实例化每个枚举值。即 O(n) 而不是 O(1)。

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

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

【讨论】:

【参考方案18】:

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

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

【讨论】:

【参考方案19】:
enum WeekDays : String , CaseIterable

  case monday = "Mon"
  case tuesday = "Tue"
  case wednesday = "Wed"
  case thursday = "Thu"
  case friday = "Fri"
  case saturday = "Sat"
  case sunday = "Sun"


var weekdays = WeekDays.AllCases()

print("\(weekdays.count)")

【讨论】:

【参考方案20】:

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

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

它还解决了我在上面和其他地方读到的“编号差距”和“迭代时间过长”的问题。

想法是将 EnumIntArray 协议添加到您的枚举中,然后通过调用 enumIntArray 函数定义一个“全部”静态变量,并为其提供第一个元素(如果编号中有间隙,则提供最后一个元素)。

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

示例(无间隙):

enum Animals:Int, EnumIntArray
 
  case Cat=1, Dog, Rabbit, Chicken, Cow
  static var all = enumIntArray(Animals.Cat)

示例(有间隙):

enum Animals:Int, EnumIntArray
 
  case Cat    = 1,  Dog, 
  case Rabbit = 10, Chicken, Cow
  static var all = enumIntArray(Animals.Cat, Animals.Cow)

这是实现它的代码:

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   

【讨论】:

【参考方案21】:

或者你可以在枚举之外定义_count,然后静态附加:

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 这样做,请删除此答案)

【讨论】:

【参考方案22】:

以下方法来自CoreKit,与其他人建议的答案相似。这适用于 Swift 4。

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())
    


enum Weekdays: String, EnumCollection 
    case sunday, monday, tuesday, wednesday, thursday, friday, saturday

那么您只需拨打Weekdays.allValues.count即可。

【讨论】:

【参考方案23】:
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

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

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

【讨论】:

【参考方案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()
        
    

【讨论】:

【参考方案25】:

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

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,否则我推荐此解决方案。

【讨论】:

向您的枚举添加一个实际上并不代表枚举类型的值是一种不好的代码气味。我发现甚至很难证明包括“ALL”或“NONE”是合理的,尽管有时它可能很诱人。包含一个“COUNT”只是为了解决这个问题非常糟糕。 臭吗?如果你想这么称呼它。表现?是的。由开发人员决定利弊。这实际上与上面 Zorayr 的答案相同,他对此进行了更详细的说明,并且新接受的答案也相似。但是直到 swift 为它添加一个 api;这是我们中的一些人决定使用的。您可以添加一个函数来验证 guards 与 COUNT 的枚举值并引发错误、返回 false 等,以解决您对类型表示的担忧。

以上是关于如何获得 Swift 枚举的计数?的主要内容,如果未能解决你的问题,请参考以下文章

Swift枚举使用String值获取Int Enum的值

Swift - GCD 可变数组多线程发出“在枚举时发生突变”

Swift:枚举编码如何获取原始值[重复]

带参数的 Swift 枚举:如何比较它们?

Swift - 如何使用枚举为 UITableView 制作自定义标题部分?

如何在 Objective-C 中使用 Swift 字符串枚举?