LoginSignup
43
24

More than 3 years have passed since last update.

Swift の enum で複数の RawValue を扱う

Last updated at Posted at 2018-05-13

※ 本記事の実装方法を刷新した unsafeBitCast を利用しない安全な実装を掲載しました。
よろしければこちらも御覧ください。
【Swift 5.1】enumで複数のRawValueを扱う


モチベーション

RawValue を持つ列挙(raw value style enum)には2つの特徴があります。

  • RawValue の値から初期化
  • RawValue の値の取得
enum Enumeration: Int {
  case a = 1
  case b
  case c
}

Enumeration(rawValue: 1)! // 1 で初期化
Enumeration.a.rawValue // 1 を取得

今回の記事では、上記の特徴を複数扱うことを考えます。

enum Enumeration {

  // Int の RawValue
  init?(rawValue1: Int)
  var rawValue1: Int { get }

  // String の RawValue
  init?(rawValue2: String)
  var rawValue2: String { get }
}

もう少し具体的な例を考えます。
たとえば、日本の都道府県を enum で定義して、
都道府県コードと都道府県名の2つ値を RawValue として扱いたい場合が、
今回扱いたい内容に該当します。

enum Prefecture {

  case hokkaido
  case aomori
  ...
  case okinawa

  // 都道府県コード(JIS X 0401都道府県コード)
  init?(code: Int)
  var code: Int { get }

  // 都道府県名
  init?(name: String)
  var name: String { get }
}

この場合は raw value style enum ではないので、独自の実装が必要です。
正攻法だと、各 case と都道府県コード、都道府県名を switch などで対応させることになります。

あるいは Int または String の raw value style enum として定義しておき、
残りの値を独自で実装する方法です。

/* 都道府県コードの raw value style enum で定義する */
enum Prefecture: Int {

  case hokkaido = 1
  case aomori
  ...
  case okinawa

  /* 都道府県名は自前で実装する必要がある */
  // 都道府県名
  init?(name: String) {
    // 各都道府県に分岐して初期化
    switch name {
    case "北海道":
        self = .hokkaido
    case "青森県":
        self = .aomori
    ...
    case "沖縄県":
        self = .okayama
    default:
        return nil
    }
  }

  var name: String {
    // 各都道府県に分岐して都道府県名を返す
    switch self {
    case .hokkaido:
        return "北海道"
    case .aomori:
        return "青森県"
    ...
    case .okinawa:
        return "沖縄県"
    }
  }
}

いずれにしても、分かりきっていることを switch していくのは面倒です。
もう少しだけ実装をシンプルにしてみます。

enum の unsafeBitCast

unsafeBitCast を使うと、同一メモリレイアウトのオブジェクト間でキャストができます。
raw value style enum に対して使った場合は、
case の位置を対応させて、異なる enum へキャストすることができます。

enum 方位: String {
    case , , , 西
}

enum CompassPoint: String {
    case north, south, east, west
}

// Swift の型システムを無視して enum 間でキャストできる
let orientation = 方位. // 東
unsafeBitCast(orientation, to: CompassPoint.self) // east

これを利用すれば、異なる enum の RawValue を使った初期化や、値の取得ができます。

// 文字列 "north" から 方位.北 を作る
let rawValue = "north"
unsafeBitCast(CompassPoint(rawValue: rawValue)!, to: 方位.self) // 方位.北

// CompassPoint.south から文字列 "南" を取得
let point = CompassPoint.south
unsafeBitCast(point, to: 方位.self).rawValue // "南"

1つ注意する点として、C で定義した enum と Swift で定義した enum でのキャストはできません。
メモリレイアウトが異なるためです。

たとえば、UIKit で定義されている UIAlertActionStyle は C の enum のため、
上述の CompassPointunsafeBitCast しようとすると実行時にエラーになります。

unsafeBitCast(UIApplicationState.active, to: CompassPoint.self) // 実行時エラー
unsafeBitCast(UIApplicationState.active, to: UIAlertActionStyle.self) // OK

都道府県の例

unsafeBitCast を使えば、都道府県コードを持つ enum と、
都道府県名を持つ enum との間で変換ができるので、
複数の RawValue を持っているように見せることができます。

PrefectureCode.swift
/// 都道府県コード
internal enum PrefectureCode: Int {
    case hokkaido = 1
    case aomori
    case iwate
    case miyagi
    case akita
    case yamagata
    case fukushima
    case ibaraki
    case tochigi
    case gunma
    case saitama
    case chiba
    case tokyo
    case kanagawa
    case niigata
    case toyama
    case ishikawa
    case fukui
    case yamanashi
    case nagano
    case gifu
    case shizuoka
    case aichi
    case mie
    case shiga
    case kyoto
    case osaka
    case hyogo
    case nara
    case wakayama
    case tottori
    case shimane
    case okayama
    case hiroshima
    case yamaguchi
    case tokushima
    case kagawa
    case ehime
    case kochi
    case fukuoka
    case saga
    case nagasaki
    case kumamoto
    case oita
    case miyazaki
    case kagoshima
    case okinawa
}
PrefectureName.swift
/// 都道府県名
internal enum PrefectureName: String {
    case 北海道
    case 青森県
    case 岩手県
    case 宮城県
    case 秋田県
    case 山形県
    case 福島県
    case 茨城県
    case 栃木県
    case 群馬県
    case 埼玉県
    case 千葉県
    case 東京都
    case 神奈川県
    case 新潟県
    case 富山県
    case 石川県
    case 福井県
    case 山梨県
    case 長野県
    case 岐阜県
    case 静岡県
    case 愛知県
    case 三重県
    case 滋賀県
    case 京都府
    case 大阪府
    case 兵庫県
    case 奈良県
    case 和歌山県
    case 鳥取県
    case 島根県
    case 岡山県
    case 広島県
    case 山口県
    case 徳島県
    case 香川県
    case 愛媛県
    case 高知県
    case 福岡県
    case 佐賀県
    case 長崎県
    case 熊本県
    case 大分県
    case 宮崎県
    case 鹿児島県
    case 沖縄県
}

最後に、変換処理を隠蔽した enum を作れば出来上がりです。

Prefecture.swift
/// 都道府県
public enum Prefecture {
    case hokkaido
    case aomori
    case iwate
    case miyagi
    case akita
    case yamagata
    case fukushima
    case ibaraki
    case tochigi
    case gunma
    case saitama
    case chiba
    case tokyo
    case kanagawa
    case niigata
    case toyama
    case ishikawa
    case fukui
    case yamanashi
    case nagano
    case gifu
    case shizuoka
    case aichi
    case mie
    case shiga
    case kyoto
    case osaka
    case hyogo
    case nara
    case wakayama
    case tottori
    case shimane
    case okayama
    case hiroshima
    case yamaguchi
    case tokushima
    case kagawa
    case ehime
    case kochi
    case fukuoka
    case saga
    case nagasaki
    case kumamoto
    case oita
    case miyazaki
    case kagoshima
    case okinawa
}

// MARK: -
extension Prefecture {

    /// 都道府県コードで初期化
    public init?(code: Int) {
        self.init(PrefectureCode(rawValue: code))
    }

    /// 都道府県名で初期化
    public init?(name: String) {
        self.init(PrefectureName(rawValue: name))
    }

    /// 都道府県コード
    public var code: Int {
        return converted(PrefectureCode.self).rawValue
    }

    /// 都道府県名
    public var name: String {
        return converted(PrefectureName.self).rawValue
    }

    private init?<T>(_ t: T?) {
        guard let t = t else { return nil }
        self = unsafeBitCast(t, to: Prefecture.self)
    }

    private func converted<T>(_ t: T.Type) -> T {
        return unsafeBitCast(self, to: t)
    }
}

これで switch を使わずに、複数の RawValue を1つの enum で扱うことができるようになりました。

Prefecture(code: 1)!.name // 北海道
Prefecture(name: "東京都")!.code // 13
43
24
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
43
24