如何确定Swift enum中的案例数?
(我希望避免手动枚举所有值,或者如果可能的话使用旧的“enum_count技巧”。)
如何确定Swift enum中的案例数?
(我希望避免手动枚举所有值,或者如果可能的话使用旧的“enum_count技巧”。)
当前回答
我定义了一个可重用的协议,它根据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)
其他回答
我为自己解决了这个问题,创建了一个协议(EnumIntArray)和一个全局实用函数(EnumIntArray),可以很容易地将“All”变量添加到任何enum(使用swift 1.2)。“all”变量将包含枚举中所有元素的数组,因此您可以使用all。算一算
它只适用于使用Int类型原始值的枚举,但也许它可以为其他类型提供一些启发。
它还解决了我在上面和其他地方读到的“编号差距”和“迭代时间过多”问题。
其思想是将EnumIntArray协议添加到枚举,然后通过调用EnumIntArray函数定义一个“all”静态变量,并为其提供第一个元素(如果编号有间隙,则为最后一个元素)。
因为静态变量只初始化了一次,所以遍历所有原始值的开销只会对程序造成一次冲击。
示例(无空白):
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
}
这种函数能够返回枚举的计数。
斯威夫特2:
func enumCount<T: Hashable>(_: T.Type) -> Int {
var i = 1
while (withUnsafePointer(&i) { UnsafePointer<T>($0).memory }).hashValue != 0 {
i += 1
}
return i
}
斯威夫特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
}
对于我的用例,在一个代码库中,多人可以向一个枚举添加键,这些情况都应该在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的开销
如果实现不反对使用整数enum,你可以添加一个额外的成员值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!")
}
}
此函数依赖于2个未记录的当前(Swift 1.1) enum行为:
枚举的内存布局只是一个大小写索引。如果case count为2 ~ 256,则为UInt8。 如果枚举从无效的大小写索引进行位转换,则其hashValue为0
所以请自担风险使用:)
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