LoginSignup
62

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-06-21

注意書き

※この記事は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でも問題なく動作しています。

以前書いた記事とか

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62