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


注意書き

※この記事はSwift4.1までのものです。
Swift4.2からは CaseIterable が搭載されたのでそちらをお使いください

enum SomeType:String, CaseIterable {
    case A, B, C, D
}

print(SomeType.allCases)


概要

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

以前書いた記事とか