Swift3.0版、Enumのcaseを配列で返すProtocol Extension


注意書き

※この記事はSwift4.1までのものです。

Swift4.2からは CaseIterable が搭載されたのでそちらをお使いください

enum SomeType:String, CaseIterable {

case A, B, C, D
}

print(SomeType.allCases)

https://github.com/apple/swift-evolution/blob/master/proposals/0194-derived-collection-of-enum-cases.md



概要

以前に、 enumの列挙子を配列で取得するのをprotocolでという記事で、Enumで宣言したcaseを配列で返すprotocolを紹介したのですが、

Xcode 8 betaSwift3.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をもとにしているので、今後実装方法が変わる可能性もあります。

その場合は適宜記事を編集して最新のものにします。


実装


swift3.0でのEnumEnumerable

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

// これはNG

enum OtherType: EnumEnumerable {
case hoge(String)
case fuga(Int)
}


Swift2.xまでとの違い

ちなみに、Swift2.xまでの書き方は以下のようになっています。


Swift2.3までのEnumEnumerable

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でも問題なく動作しています。


以前書いた記事とか