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

はじめに

本投稿は、Swift愛好会 Advent Calendar 2017の11日目の投稿になります。
前日の10日目は、@yutailang0119さんの「Xcode PlaygroundでPlatformによる分岐をする」という記事になります。
翌日の12日目は、@dekatotoroさんの「RxSwiftのDebounceとThrottle」という記事になります。

Swift3までのprotocolのassociatedtype

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

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

// 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というFluxデザインパターンのサポートフレームワークに実装してみました。
FluxCapacitorでは

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

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

Swift3までの実装

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

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のobserverDataStoreStoreのインスタンスが存在しない場合は、StoreとActionはお互いの型を知らないためStoreの生成をすることができず、値をdipatchすることができなくなります。
そのため、application(_:didFinishLaunchingWithOptions:)や最初に呼ばれるViewControllerでStore.instantiate()を予め行う必要がありました。

flux.001.png

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

Swift4からの実装

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

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

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

最後に

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


『 Swift 』Article List