注意書き
※この記事はSwift4.1までのものです。
Swift4.2からは CaseIterable
が搭載されたのでそちらをお使いください
enum SomeType:String, CaseIterable {
case A, B, C, D
}
print(SomeType.allCases)
概要
以前に、 enumの列挙子を配列で取得するのをprotocolでという記事で、Enumで宣言したcaseを配列で返すprotocolを紹介したのですが、
Xcode 8 beta
とSwift3.0 preview-1
が出たということで、swift3.0に対応するように書き換えてみました。
なぜ作ったか
enum に宣言した全てのcaseが配列でほしい時に、
enum SomeType {
case A, B, C, D
var cases: [SomeTypes] {
return [.A, .B, .C, .D]
}
}
としてしまうのは、後にcaseが増えた時に 漏れる可能性 もあるし、他のEnumに対しても同じことをやろうと思った時に書き方が 冗長的 になってしまうので、
protocolとextensionを活用して、
enum SomeType: EnumEnumerable {
// ...
}
let allCases: [SomeType] = SomeType.cases // => [A, B, C, D]
とするだけで、caseの配列が取得できるようにしてみました。
はじめに
2016/06/20現在、Xcode 8 beta、Swift3.0 preview-1をもとにしているので、今後実装方法が変わる可能性もあります。
その場合は適宜記事を編集して最新のものにします。
実装
public protocol EnumEnumerable {
associatedtype Case = Self
}
public extension EnumEnumerable where Case: Hashable {
private static var iterator: AnyIterator<Case> {
var n = 0
return AnyIterator {
defer { n += 1 }
let next = withUnsafePointer(to: &n) {
UnsafeRawPointer($0).assumingMemoryBound(to: Case.self).pointee
}
return next.hashValue == n ? next : nil
}
}
public static func enumerate() -> EnumeratedSequence<AnySequence<Case>> {
return AnySequence(self.iterator).enumerated()
}
public static var cases: [Case] {
return Array(self.iterator)
}
public static var count: Int {
return self.cases.count
}
}
使い方
Xcode 8 betaで開いたPlaygroundにコピペしてサッと試すことができます。
任意のEnumに対して、 EnumEnumerable を適合させるだけです。
enum Fruit: Int, EnumEnumerable {
case apple
case banana
case orange
}
_ = Fruit.cases // => [apple, banana, orange]
Fruit.cases.count // => 3
ただし、以下のようにAssociated ValueをもつEnumには使えません。
// これはNG
enum OtherType: EnumEnumerable {
case hoge(String)
case fuga(Int)
}
Swift2.xまでとの違い
ちなみに、Swift2.xまでの書き方は以下のようになっています。
public protocol EnumEnumerable {
associatedtype Case = Self
}
public extension EnumEnumerable where Case: Hashable {
private static var generator: AnyGenerator<Case> {
var n = 0
return AnyGenerator {
defer { n += 1 }
let next = withUnsafePointer(&n) { UnsafePointer<Case>($0).memory }
return next.hashValue == n ? next : nil
}
}
@warn_unused_result
public static func enumerate() -> EnumerateSequence<AnySequence<Case>> {
return AnySequence(generator).enumerate()
}
public static var cases: [Case] {
return Array(generator)
}
public static var count: Int {
return cases.count
}
}
変更点に関しては以下の通りです。
- AnyGenerator →
AnyIterator
- UnsafePointer.memory → UnsafePointer.
pointee
- EnumerateSequence →
EnumeratedSequence
- AnySequence.enumerate() →
AnySequence.enumerated()
いずれも、Swift3.0で承認されたAPI Guidelineに則った形でStandard Libraryに変更が適応されたことによる影響で、リネームされています。
SE-0006あたりに書いてあります。
それ以外はSwift3.0でも問題なく動作しています。