はじめに
本投稿は、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
というものが存在しています。
Actionable
とStorable
に、それぞれ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のobserverDataStore
にStoreのインスタンスが存在しない場合は、StoreとActionはお互いの型を知らないためStoreの生成をすることができず、値をdipatchすることができなくなります。
そのため、application(_:didFinishLaunchingWithOptions:)
や最初に呼ばれるViewControllerで**Store.instantiate()**を予め行う必要がありました。
上図のように、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()
を行う必要がなくなります。
このようにして、Swift4からprotocolのassociatedtypeでできるようになったことを利用することで、使い勝手をより良くすることができました。
最後に
protocolのassociatedtypeでお互いを定義し合いたくなることが地味にあると思うので、Swift4を利用している方は是非試してみてください!