yashigani?.days

週刊少年ジャンプについてだらだら書きます

SwiftでNS_OPTIONSスタイルのEnumを実現する

Swiftenumは便利ですが,たまには使い慣れたNS_OPTIONSスタイルのenumを作りたくなるときがあります. 素朴にやるとこのように実装できます.

/// My Options
enum Options: UInt {
  case None = 0
  case A    = (1 << 0)
  case B    = (1 << 1)
}

しかし,これでは全ての組み合わせのパターンをcaseとして用意する必要がありますし,用意したところで常にrawValueでの比較が必要です.

// My Options doesn't have it!
let options: Options = Options(rawValue: .A.rawValue | .B.rawValue)

こんなのはとてもじゃないけど使えません.

UIViewAutoresizingの実装を見てみる

UIKitにはNS_OPTIONSを使ったenumがいくつもあり,それらはSwiftでもObjective-Cのときと同じように使うことができます. 実際にはどう実装されているのでしょうか? 代表的なNS_OPTIONSを使ったenumである,UIViewAutoresizingを例に実装を見てみます.

struct UIViewAutoresizing : RawOptionSetType {
    init(_ rawValue: UInt)
    init(rawValue: UInt)

    static var None: UIViewAutoresizing { get }
    static var FlexibleLeftMargin: UIViewAutoresizing { get }
    static var FlexibleWidth: UIViewAutoresizing { get }
    static var FlexibleRightMargin: UIViewAutoresizing { get }
    static var FlexibleTopMargin: UIViewAutoresizing { get }
    static var FlexibleHeight: UIViewAutoresizing { get }
    static var FlexibleBottomMargin: UIViewAutoresizing { get }
}

素朴にenumで実装するわけではなく,structで実装されています. 通常Swiftenumcaseで各値を決めますが,全てstaticpropertyとして定義されているのがおもしろいです.

Objective-Cのときと同じように使える秘密は,RawOptionSetTypeにあってこのような継承関係を持っています.

RawOptionSetType
       |
       |----------------------------------------------
       |                      |                      |
_RawOptionSetType   BitwiseOperationsType   NilLiteralConvertible
       |
       |----------------
       |               |
RawRepresentable   Equatable

これらを実装するとこうなります.

/// My Options
struct Options : RawOptionSetType {
    typealias RawValue = UInt
    private var value: UInt = 0
    init(_ value: UInt) { self.value = value }
    init(rawValue value: UInt) { self.value = value }
    init(nilLiteral: ()) { self.value = 0 }
    static var allZeros: Options { return self(0) }
    static func fromMask(raw: UInt) -> Options { return self(raw) }
    var rawValue: UInt { return self.value }

    static var None: Options { return self(0) }
    static var A: Options { return Options(1 << 0) }
    static var B: Options { return Options(1 << 1) }
}

これでUIViewAutoresizingと同じように使えます.

let options: Options = .A | .B

大変な実装をすることでようやくNS_OPTIONS相当のstructを手に入れることができました. 深淵なるSwiftのテクニックに触れることができて,大満足ですね.

お得情報コーナー

こんなに実装してらんねえ!と思ったアナタに朗報です! Bridging Headerにこのように書いてみましょう.

typedef NS_OPTIONS(Options, NSUInteger) {
  OptionsNone,
  OptionsA,
  OptionsB,
};

これで大変な実装なしにSwiftにexportされます. お手軽ですね!

pure Swiftで実装したいというアナタにはこれがオススメです.

このジェネレータを使えばメッチャ簡単にNS_OPTIONS相当のstructの実装が作れます.

参考

RawOptionSetTypeについての解説です. かなり初期に書かれたものなので,現在は微妙に仕様が変わっていますが参考になるとおもいます.