はじめに
iOS Advent Calendar 2018、13日目です。
個人開発のアプリを改修しようと思い、最近気に入ってる設計のFluxを取り入れつつ、GraphQL API を使用したいことから、 ReactorKit と apollo-ios を使って開発をしています。
少し、ニッチな話になってしまいますが、ReactorKit
と apollo-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 {
//
}
}
extension
でEquatable
を適応させるときには、暗黙的に==
が実装されないので、毎回自分で実装を書かなくてはなりません。
毎回、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なので、このように書くことはできません。
GraphQLSelectionSet
を distinctUntilChanged()
に対応させる
そこで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
の機構を作ろうとすると色々と大変なことがありました。
もっと良さそうな解決方法がありましたら、ぜひ教えてください。