CombineでflatMapを安全に利用したい
noteのiOS版アプリでは昨年の中頃からAppleが一昨年リリースしたCombine.frameworkの利用を始めました。それまではHydraというPromiseの利用が可能なライブラリを利用していましたが、iOS 12以下のサポートを切るタイミングで乗り換えを始めました。とはいえHydraの時からそうでしたが利用可能な箇所を全て利用するという方針ではなく基本的に非同期処理が辛そうな部分で集中的に利用するようにしています。よく利用するのはAPI通信部分ですね。個人的にはNotificationCenterのpublisherもよく利用しています。(cancellableをまとめられるのが便利)
flatMapでちょっと悩んだ
Combineを便利に利用する一方少し悩むこともありました。それがflatMapを利用する際の実装についてです。クロージャーでキャプチャが発生する場合は[weak hoge]などで安全に実装することが多いかと思います。ただ、以下のような場合にどうflatMapを実装すればいいのか悩みました。
import Combine
import UIKit
struct A {}
struct B {
var name: String
}
final class SomeRepository {
func getData() -> AnyPublisher<A, Never> { ... }
func getRelatedData(with a: A) -> AnyPublisher<B, Never> { ... }
}
final class SomeViewController: UIViewController {
let repository = SomeRepository()
private var cancellables: Set<AnyCancellable> = .init()
override func viewDidLoad() {
super.viewDidLoad()
// TODO: repositoryのgetData、getRelatedDataを呼んでBの名前を表示したい
}
private func showName(with b: B) { ... }
}
とりあえずweak selfを試みます。
repository.getData()
.flatMap { [weak self] in self?.repository.getRelatedData(with: $0) }
ですよね。OptionalにラップされてしまいPublisherが返せなくなってしまいます。ここで考えるのはunowned selfですがunownedに家族が殺された人は数知れず...個人的にはあまり使いたくなかったので悩みました。そこで思いついたのがcompactMapです。
repository.getData()
.compactMap { [weak self] in self?.repository.getRelatedData(with: $0) }
.flatMap { $0 }
.sink { [weak self] in
self?.showName(with: $0)
}
.store(in: &cancellables)
compactMapを利用することでunwrapされた値が流れてくるのでその値を利用してflatMapしてやれば綺麗にストリームを変換することができました。RxSwiftにはcompactMapが無いので色々工夫があるみたいですがCombineだとシンプルに実装できて良さそうです。
まとめ
個人的にRxなどのリアクティブなフレームワークには苦手意識があったのですが、副業でRxSwiftを利用したりnoteでもCombineを導入したりと触れる機会が増えてきたのでなんとなく苦手意識も減ってきたような気がします。まだまだ、使ってないオペレータは沢山ありますが、引き続き色々試してみたいなと思います。他にもいい方法があれば知りたいです。
追記(2021/01/08 9:50)
ひろんさんからEmpty<B, Never>を利用すればどうだと教えていただきました。
こちらでも問題なく動作することはわかりました。教えていただきありがとうございます!
この記事が気に入ったらサポートをしてみませんか?