Swift4からprotocolのassociatedtypeでできるようになったこと

# はじめに

本投稿は、[Swift愛好会 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/love_swift)の11日目の投稿になります。
前日の10日目は、[@yutailang0119](https://qiita.com/yutailang0119)さんの「[Xcode PlaygroundでPlatformによる分岐をする](https://qiita.com/yutailang0119/items/be3cd12626ea8ce27ad8)」という記事になります。
翌日の12日目は、[@dekatotoro](https://qiita.com/dekatotoro)さんの「[RxSwiftのDebounceとThrottle](https://qiita.com/dekatotoro/items/be22a241335382ecc16e)」という記事になります。


# Swift3までのprotocolのassociatedtype

Swift3まではprotocolのassociatedtypeでお互いを定義していると、下記のようなエラーになっていました。

![](https://qiita-image-store.s3.amazonaws.com/0/60325/a682379a-5a69-d76b-f601-748506b4c003.png)

しかし、Swift4からはprotocolのassociatedtypeでお互いを定義することができるようになったので、下記のような実装が可能になりました。

```swift
// protocol
protocol A {
    associatedtype BType: B
    init()
}

protocol B {
    associatedtype AType: A
    init()
}

// protocolの実装
struct AImpl: A {
    typealias BType = BImpl
}

struct BImpl: B {
    typealias AType = AImpl
}

// Aの生成
func makeA<T: B>(from b: T.Type) -> T.AType {
    return T.AType()
}

let a = makeA(from: BImpl.self)
```

# 実際の利用方法

相互に定義しているprotocolのassociatedtypeを、[FluxCapacitor](https://github.com/marty-suzuki/FluxCapacitor)というFluxデザインパターンのサポートフレームワークに実装してみました。
FluxCapacitorでは

- Actionを実装するためのprotocolである`Actionable`
- Storeを実装するためのprotocolである`Storable`
- ActionとStoreを間接的に繋げるためのprotocolである`DisaptchValue`

が存在し、それぞれを採用したオブジェクトを実装することで、Fluxデザインパターンを容易に利用できるようになっています。（いろんな名称を**Back to the future**に出てくるものの名称にしてしまったため、ネタライブラリっぽく見えますがちゃんと使えます！）
本投稿では、User関連に関するFluxの要素を実装した例で進めていきます。

## Swift3までの実装

上記でも記載しましたが、FluxCapacitorではActionとStoreを紐付けるために、`DispatchValue`というものが存在しています。
`Actionable`と`Storable`に、それぞれassociatedTypeとしてDispatchValueを定義する形になっています。

```swift
public protocol DispatchValue {}

extension DispatchValue {
    static var dispatchKey: String {
        return String(describing: self)
    }
}

public protocol Actionable {
    associatedtype DispatchValueType: DispatchValue
}

public protocol Storable: class {
    associatedtype DispatchValueType: DispatchValue
    init(dispatcher: Dispatcher)
}

extension Storable {
    static var dispatcher: Dispatcher { return .shared }

    public static func instantiate() -> Self {
        return dispatcher.observerDataStore.object(for: Self.self) ?? .init(dispatcher: dispatcher)
    }
}
```



そして、FluxCapacitorを利用する場合にStoreのインスタンスは、Storableにデフォルト実装されている`static func instantiate() -> Self`から取得することができます。
`instantiate()`が呼び出されると、`DispatcherのobserverDataStore`に該当のStoreがあればそのインスタンスを返し、存在していなければStoreを生成してから`DispatchValue`をキーとして`observerDataStore`に保持させてインスタンスを返すようになっています。

値をセットする場合は、Actionから該当の値をDispatcherを介してStoreにdispatchします。
その際に、`DispatcherのobserverDataStore`に**Storeのインスタンス**が存在しない場合は、StoreとActionはお互いの型を知らないためStoreの生成をすることができず、値をdipatchすることができなくなります。
そのため、`application(_:didFinishLaunchingWithOptions:)`や最初に呼ばれるViewControllerで**Store.instantiate()**を予め行う必要がありました。

![flux.001.png](https://qiita-image-store.s3.amazonaws.com/0/60325/70b045cc-26e0-3f5a-3d7a-6cd03e3915ae.png)

上図のように、Storeのインスタンスが`DispatcherのobserverDataStore`に保持される前にActionを呼んでしまうと、該当の値は取得できなくなっています。

## Swift4からの実装

Storeの生成よりもActionが先に呼ばれてしまった場合の問題を解決する方法として、`Actionable` <-> `DispatchValue`と`Storable` <-> `DispatchValue`にassociatedtypeとしてお互いを定義します。

```swift
public protocol DispatchValue {
    associatedtype RelatedStoreType: Storable
    associatedtype RelatedActionType: Actionable
}

public protocol Actionable {
    associatedtype DispatchValueType: DispatchValue
}

public protocol Storable: class {
    associatedtype DispatchValueType: DispatchValue
}
```

上記のような定義をすることで、Actionに紐付いているStoreの型をDispatchValueを介して取得できるようになります。
よって、`DispatcherのobserverDataStore`に該当のStoreが存在しなかった場合は、下図のように**DispatchValueのRelatedStoreType**からStoreを生成し、`DispatchValue`をキーとして`observerDataStore`に保持させることができるようになり、予め`Store.instantiate()`を行う必要がなくなります。

![flux.002.png](https://qiita-image-store.s3.amazonaws.com/0/60325/379585e6-927b-77ed-cea5-f668d5f225b7.png)

このようにして、**Swift4からprotocolのassociatedtypeでできるようになったこと**を利用することで、使い勝手をより良くすることができました。

# 最後に

protocolのassociatedtypeでお互いを定義し合いたくなることが地味にあると思うので、Swift4を利用している方は是非試してみてください！