Edited at
iOSDay 13

ReactorKit と apollo-ios を少し効率的に併用する方法


はじめに

iOS Advent Calendar 2018、13日目です。

個人開発のアプリを改修しようと思い、最近気に入ってる設計のFluxを取り入れつつ、GraphQL API を使用したいことから、 ReactorKitapollo-ios を使って開発をしています。

少し、ニッチな話になってしまいますが、ReactorKitapollo-ios を少し効率的に併用する方法について考えたことを紹介します。


ReactorKit と distinctUntilChanged()

ReactorKitでは、stateの一部が変更されただけでも、state全部が流れてくるので、適切にdistinctUntilChanged()を呼ぶ必要があります。

reactor.state.map { $0.elements }

.distinctUntilChanged()
.subscribe(onNext: { _ in
//
})
.disposed(by: disposeBag)

distinctUntilChanged()を呼ぶためには流れてくる要素がEquatableに準拠している必要があります。

流れてくる要素が自分で用意した構造体なのであれば、

struct Element: Equatable {

//
}

↑のようにするだけで, Swift4.1からは暗黙的に==が実装されます。


apollo-ios と Equatable

しかし、今回は、apollo-iosによって自動で生成された構造体が流れてくる場合がほとんどでした。

extension GeneratedObject: Equatable {

public static func == (lhs: Element, rhs: Element) -> Bool {
//
}
}

extensionEquatableを適応させるときには、暗黙的に==が実装されないので、毎回自分で実装を書かなくてはなりません。

毎回、Equatableを意識しなければいけないのは、少し面倒くさいのでもうちょっと効率よくしていきたいところです。

apollo-iosによって生成された構造体は、GraphQLSelectionSetに準拠しています。

GraphQLSelectionSetは 通信で取得した情報を格納したDictionary型のresultMap を持っているので、これを比較すれば値に変更があったかどうか判断することができそうです。

つまり、次のように書くことができれば、解決できそうです。

extension GraphQLSelectionSet: Equatable {

public static func == (lhs: Element, rhs: Element) -> Bool {
return lhs.resultMap == rhs.resultMap
}
}

しかし、GraphQLSelectionSet はProtocolなので、このように書くことはできません。


GraphQLSelectionSetdistinctUntilChanged() に対応させる

そこでObservableTypeの中身がGraphQLSelectionSetのときにのみ適応されるdistinctUntilChanged()を作ってしまう手があります。

extension ObservableType where E: GraphQLSelectionSet {

func distinctUntilChanged() -> Observable<Self.E> {
return distinctUntilChanged { $0.resultMap == $1.resultMap }
}
}

これによって、中身が何であってもapollo-iosによって自動生成された構造体であればdistinctUntilChanged()を呼ぶことが可能になりました。


[GraphQLSelectionSet] の対応

しかし、これですべて解決かと思いきや、これだけで終わると、[GraphQLSelectionSet]に対応できていません。

先ほどと同じように、[GraphQLSelectionSet] 用の distinctUntilChanged() を作成すれば一見解決するように思えますが、結局[[GraphQLSelectionSet]] のような場合に、また同じ問題にぶつかってしまいます。

Equatableの場合は、Swift4.2から、要素がEquatbleに準拠しているときは[Equtable]Equatbleに準拠するようになりました。(Conditional conformances)

少し強引な気もしますが、同じように要素がGraphQLSelectionSetの場合、[GraphQLSelectionSet]GraphQLSelectionSetに準拠するようにすれば、配列であっても対応することができそうです。

extension Array: GraphQLSelectionSet where Element: GraphQLSelectionSet {

public init(unsafeResultMap: ResultMap) {
let resultMaps = unsafeResultMap["resultMaps"] as! [ResultMap]
let sets = resultMaps.map(Element.init)
self = sets
}

public static var selections: [GraphQLSelection] {
return []
}

public var resultMap: ResultMap {
return ["resultMaps": self.map { $0.resultMap }]
}
}

これによって、apollo-iosによって生成された構造体をdistinctUntilChanged()したいときに、毎回Equatableを気にする必要がなくなりました!


最後に

相性が微妙なのであれば最初からReactorKit を使わなければいいのでは.. という思いもありましたが、自分でFluxの機構を作ろうとすると色々と大変なことがありました。

もっと良さそうな解決方法がありましたら、ぜひ教えてください。