型推論が効いた幸せなSegueの世界

## なりゆき
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">segueのidentifierってどうやってつけてるんだろみんな　文字リテラル使いたくないから行き先のViewControllerのクラス名使ってString(HogeViewController.self)ってやってるけど</p>&mdash; ひらり (@hiragram) <a href="https://twitter.com/hiragram/status/716926961642156033">2016年4月4日</a></blockquote>

とりあえず事の顛末はツイートを辿ってもらえば全部理解できるんですが折角なので共有をします。
クラス名使ってると、UINavigationControllerとかUITabBarControllerに遷移しようものなら即死ですし、でなくとも複数の画面から指してる場合も死ねますね。よろしくない。
せっかくSwift使ってるんですから幸せになりたいものです。

## レベル1 (enum + extension)

Protocolを宣言してUIViewControllerにextensionを生やします。

```swift
protocol SegueType {
    var rawValue: String { get }
}

extension UIViewController {
    func performSegue(segue: SegueType, sender: AnyObject?) {
        performSegueWithIdentifier(segue.rawValue, sender: sender)
    }
}
```

ViewControllerにSegueを実装してStringとSegueTypeを宣言、caseにstoryboardで定義したsegue identifierを列挙します。

```swift
class MyViewController: UIViewController {
    enum Segue: String, SegueType {
        case ToNext
    }
}

myvc.performSegue(MyViewController.Segue.ToNext, sender: nil)
```

ね、簡単でしょ？
enumにstoryboardに書いたidentifierを移すだけの簡単なお仕事です。
え？prepareForSegueが幸せじゃない？
しょうがないにゃぁ(´・ω・｀)


## レベル2 (associatedtype + extension)
今度はassociatedtypeをゴリゴリ使ってProtocolとextensionを作っていきます

```swift
protocol SegueType {
    associatedtype Target: UIViewController
    static var identifier: String { get }
}

protocol TypedSegueSourceType: class {
    var handlers: [String: (UIViewController, sender: AnyObject?) ->()] { get set }
    func addSegueHandler<S: SegueType>(segue: S.Type, handler: (S.Target, sender: AnyObject?) -> ())
}

extension TypedSegueSourceType where Self: UIViewController {
    func addSegueHandler<S: SegueType>(segue: S.Type, handler: (S.Target, sender: AnyObject?) -> ()) {
        handlers[segue.identifier] = { controller, sender in
            handler(controller as! S.Target, sender: sender)
        }
    }

    func _prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if let handler = handlers[segue.identifier!] {
            handler(segue.destinationViewController, sender: sender)
        }
    }
}
```
`SegueType`に`Target`が加わりました。これを使って`prepareForSegue`で型推論を効かせます。
`TypedSegueSourceType`という新しい`Protocol`を定義しました。Handlerを追加するfunc、格納する`handlers`を宣言します。
`handlers`を書き込み可能にするためにclass only protocolになっています。(どっちにしても`UIViewController`に付加するので問題ありません)
そして、 `extension TypedSegueSourceType where Self: UIViewController` に実装を書きます。
最後はHandlerをコールする`_prepareForSegue`を書いて準備は終了。

```swift
class MyViewController: UIViewController, TypedSegueSourceType {
    struct Segue {
        struct ToNext: SegueType {
            typealias Target = NextViewController
            static var identifier = "ToNext"
        }
    }

    var handlers: [String : (UIViewController, sender: AnyObject?) -> ()] = [:]

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        _prepareForSegue(segue, sender: sender)
    }
}

myvc.addSegueHandler(MyViewController.Segue.ToNext.self) { (target, sender) in
    // 型推論が効いてtarget: NextViewControllerになっています。ハッピー★
}
```

簡単ですね、Segueの呼び出し元となるVCに`TypedSegueSourceType`を実装して、`handlers`と`prepareForSegue`をちょいと書けば全部ヨロシク動いちゃいます。すごい。
protocolとextensionの実装にしているのは、対象がUIViewControllerをストレートに継承出来ない場合があるからです。(Navigation、TabBar、etc)

何、`UINavigationCotnroller`と`UITabBarController`はどうするか？

## レベル3 (Generics NavigationController)
UINavigationController以下に同じ型のViewControllerが格納される場合に限定しますが、GenericsなUINavigationControllerを作り、それを継承して束縛することでstoryboard上で使うことが ~~出来ます。~~出来ません。ポンコツ乙
正しくはprotocolとassociatedtypeを使って束縛します。ゴメンナサイ

```swift
protocol TypedNavigationController: class {
    associatedtype T: UIViewController
    var genericsViewControllers: [T] { get set }
    var genericsTopViewController: T? { get }
    var genericsVisibleViewController: T? { get }
}

extension TypedNavigationController where Self: UINavigationController {
    var genericsViewControllers: [T] {
        get {
            return self.viewControllers.map { $0 as! T }
        }
        set {
            self.viewControllers = newValue
        }
    }

    var genericsTopViewController: T? {
        return self.topViewController as? T
    }

    var genericsVisibleViewController: T? {
        return self.visibleViewController as? T
    }
}

class NextViewController: UINavigationController, TypedNavigationController {
    typealias T = ChildViewController
}
```

この場合、`NextViewController`はそのままstoryboardで使えます。
それ以下の型をチェックしてくれるわけではありませんが、GenericsなViewControllerをstoryboardで使うという欲求は満たせるでしょう。

もちろん、世の中にはもはや同じViewControllerしか格納されないNavigationは都市伝説なわけですし、TabBarControllerには対応できない。
TabBarControllerはせいぜい5つが限界なので、2-5までのTypedTabBarControllerのprotocolとextensionを作ればいいです。
NavigationControllerは、ChildViewControllerにおいてEmbedFrameとenumを上手く組み合わせれば、switchで分岐して推論も出来ます。
最も、NavigationController触る上で最も大事なのはrootViewControllerの型が保証されることなので、それさえできれば良い気もしますけどね。

※サンプルコード内にて`!`を多用しています。面倒くさいから使いましたが普通はLint等で弾くことをおすすめします。

## まとめ
Storyboardから触る諸々も、予め頑張っておけば型推論が効いて幸せになれます。
RxSwiftとか使うとこの辺がもっと幸せに書けるようになるんですよね(宣伝)
やってることはStoryboardと型推論を人の手で繋げているだけなので、型安全とか痴がましいことは口が裂けても言えないです。でも出来るだけ幸せになりたいんじゃ~