36
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Swift愛好会Advent Calendar 2017

Day 11

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

Last updated at Posted at 2017-12-11

はじめに

本投稿は、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 <-> `DispatchValue`と`Storable` <-> 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を利用している方は是非試してみてください!

36
29
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
36
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?