ローソンデジタルイノベーション テックブログ

ローソンデジタルイノベーション(LDI)の技術ブログです

Combineを使った開発で用意した拡張関数の紹介

はじめに

こんにちは、ローソンデジタルイノベーション(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へ置き換えを進めています。
置き換えていく中で新たに課題が生まれたり、より良い実装方法が見えてきた時など、良いネタがあればまた記事にしたいと考えています。
今後もローソンデジタルイノベーションでは技術ブログを更新していきますので、是非「読者になる」で応援していただけますと幸いです。