はじめに
こんにちは、ローソンデジタルイノベーション(LDI)でiOSエンジニアを担当している山形です。
LDIで開発しているローソン公式アプリでは、RxSwiftをCombineやSwift Concurrencyへ置き換えを進めています。
今回はCombineを使った開発の中で用意した拡張関数について紹介します。
開発環境
macOS:Ventura 13.6
Xcode:15.0
サポートOS:iOS14〜
Combineの拡張関数
Combineを使用するにあたって、ボイラープレートになり繰り返し記述されるようなものはなるべく記述しなくて良い状態にし、コードをスッキリさせることや記述忘れを防ぐことで一定の品質を保ちたいという思いのもと、いくつかの拡張関数を用意しています。
実際に用意した拡張関数をいくつか以下に記載します。
sink(with:cancellables:receiveValue:)
この拡張関数はPublisherのsink関数を使用する際に記述するボイラープレートになる部分を実装します。
Parameters
パラメータ名 | 内容 |
---|---|
owner | receiveValueで使用する弱参照オブジェクトをアンラップして提供するためのパラメータ RxSwiftの bind(with:onNext:) から着想を得ており、呼び出し側でアンラップ処理を記述しなくてよくなりコードがスッキリします。 |
cancellables | cancellablesオブジェクトが解放された時にPublisherを解放するために使用するパラメータstore(in:) を毎回記述する必要がなくなり、また呼び出し忘れを防ぎます。 |
receiveValue | 値を受け取った際に実行されるクロージャ |
コード
extension Publisher where Self.Failure == Never { func sink<Owner: AnyObject>( with owner: Owner, cancellables: inout Set<AnyCancellable>, receiveValue: @escaping ((Owner, Self.Output) -> Void)) { compactMap { [weak owner] output -> (Owner, Self.Output)? in guard let owner else { return nil } return (owner, output) } .sink(receiveValue: receiveValue) .store(in: &cancellables) } }
sinkWithMainThread(with:cancellables:receiveValue:)
この拡張関数はPublisherのsink関数で値を受け取った際に実行されるreceiveValue
クロージャをメインスレッドで実行します。
主にUIを操作する際によく使用される記述を関数化することでコードをスッキリさせます。
Parameters
sink(with:cancellables:receiveValue:)
と同様です。
コード
extension Publisher where Self.Failure == Never { func sinkWithMainThread<Owner: AnyObject>( with owner: Owner, cancellables: inout Set<AnyCancellable>, receiveValue: @escaping ((Owner, Self.Output) -> Void)) { receive(on: DispatchQueue.main) .sink(with: owner, cancellables: &cancellables, receiveValue: receiveValue) } }
sinkAsync(with:cancellables:receiveValue:)
この拡張関数はPublisherのsink関数でSwift Concurrencyの非同期関数をTaskのクロージャ内で実行します。
これにより呼び出し側でTaskを毎回記述する必要がなくなり、またネストが深くなるのを防ぎます。
Parameters
sink(with:cancellables:receiveValue:)
と同様です。
コード
extension Publisher where Self.Failure == Never { func sinkAsync<Owner: AnyObject>( with owner: Owner, cancellables: inout Set<AnyCancellable>, receiveValue: @escaping (@Sendable(Owner, Self.Output) async throws -> Void)) { sink(with: owner, cancellables: &cancellables) { owner, output in Task { try await receiveValue(owner, output) } } } }
最後に
ここまで記事を読んでいただきありがとうございます!
冒頭でお伝えした通り現在RxSwiftをCombineやSwift Concurrencyへ置き換えを進めています。
置き換えていく中で新たに課題が生まれたり、より良い実装方法が見えてきた時など、良いネタがあればまた記事にしたいと考えています。
今後もローソンデジタルイノベーションでは技術ブログを更新していきますので、是非「読者になる」で応援していただけますと幸いです。