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)起,您可以申报符合
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更新
在枚举中采用
以马丁的回答为例(并将他的回答比我的更高)
警告:下面的方法似乎不再有效。
我不知道任何计算枚举事例数的通用方法。不过,我注意到枚举案例的
例如,对于此枚举:
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} } |
我不能说这是一条规则,还是它将来会改变,所以请自担风险使用:)
我定义了一个可重用的协议,它根据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 |
当您想要枚举值,并且适用于所有枚举类型时,这也很有用。
如果实现没有任何反对使用整数枚举的内容,可以添加一个名为
1 2 3 4 5 | enum TableViewSections : Int { case Watchlist case AddButton case Count } |
现在,您可以通过调用
当您在switch语句中处理枚举时,请确保在遇到您不期望的
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} } |
在主代码中,如果有人添加了一个枚举案例
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)! } } |
计数:
索引:
享受:
此功能依赖于2个未记录的电流(Swift 1.1)
enum 的内存布局只是case 的索引。如果病例数从2到256,则为UInt8 。- 如果
enum 是由无效的case索引进行位转换,则其hashValue 是0 。
因此,请自行承担风险:)
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 |
我编写了一个简单的扩展,它给出了原始值为整数的所有枚举的
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()) } } |
不幸的是,它将
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发布的方法非常相似,但适用于所有整数类型。
当然,它不是动态的,但对于许多用途,可以通过将静态变量添加到枚举中来获得。
然后作为
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枚举的最简单计数器是添加:
最后。还有…维奥拉-现在你数到了-快速而简单
使用
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的答案。
不支持泛型
请注意,代码指挥官对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已经提到的
扩展Matthieu Riegler的答案,这是一个针对Swift 3的解决方案,不需要使用泛型,可以使用带
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 } |
或者,您只需在枚举外部定义
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 } |
这样,无论您创建了多少枚举,它都只会创建一次。
(如果
以下方法来自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 } |
然后你只需要打电话给
它可以使用静态常量,该常量包含枚举的最后一个值和一个值。
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开始的
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 } |
我仍然认为,当前选定的答案是所有枚举的最佳答案,除非您使用