ObjecTips

Swift & Objective-C で iOS とか macOS とか

prepareForSegue での segue の identifier による分岐を Swift enum で管理する

まず初めに以下の様なString型の enum を用意する。

    enum SegueIdentifier: String {
        case a
        case b
    }

Swift 5 以前

UIViewControllerprepareForSegue メソッド内で segue.identifier から enum SegueIdentifier の作成を行う。
segue.identifier の型は String? なので以下の様にguard文(もしくはif let文)で unwrap する必要がある。

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let identifier = segue.identifier,
            let segueIdentifier = SegueIdentifier(rawValue: identifier) else {
                return
        }
        switch segueIdentifier {
        case .a:
            // do something
            break
        case .b:
            // do something
            break
        }
    }

Swift 5.1

Swift 5.1 ではSwitch文に optional の enum が利用できる様になったのでguard文を一部簡略化して以下の様に書ける。
SegueIdentifier(rawValue: identifier)nil の場合に対応するために case .none: が必要になる。

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let identifier = segue.identifier else {
            return
        }
        switch SegueIdentifier(rawValue: identifier) {
        case .a:
            // do something
            break
        case .b:
            // do something
            break
        case .none:
            break
        }
    }

以下の様に case .none: の代わりに default: でもコンパイルは通るけど、その場合 enum の定義が増えた際に新しい enum の値は default にマッチしてしまうためコンパイラによる警告が出ない。.none を使っている場合は新しい enum の値が記述されておらず case が網羅されていない事をコンパイラが警告してくれるため .none を使う方が良いと思われる。

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let identifier = segue.identifier else {
            return
        }
        switch SegueIdentifier(rawValue: identifier) {
        case .a:
            // do something
            break
        case .b:
            // do something
            break
        default:
            break
        }
    }

Swift 5.1 + extension

Switch文が optional に対応した事で enum が作成出来ない時(rawValue の値が不正で初期化に失敗した時)はどうせ .none に引っかかるしわざわざnullチェックのguard文を書かなきゃいけないのは面倒だな、、という気持ちが出てきた。

  • segue.identifier の型が String ではなく String?
  • enum Foo: String = protocol RawRepresentable のイニシャライザの引数が optional を許容しない
init?(rawValue: Self.RawValue)

この2点がguard文を必須にしているため、以下の様な extension を書いて RawRepresentable に optional な引数を許容するイニシャライザを追加してみる。

gist38ded67adef6a706b05bbaeed3c1c9ff

その結果以下の様に記述できる様になる。

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // boilerplate has gone!
        switch SegueIdentifier(rawValue: segue.identifier) {
        case .a:
            // do something
            break
        case .b:
            // do something
            break
        case .none:
            break
        }
    }

extension を1つ記述するだけで全ての同様の箇所のguard文(もしくはif let文)を消去する事ができる。
ボイラープレートを減らす事ができてスッキリ。