🐍

RxSwift 6.0/6.1で弱参照なオブジェクトの参照が楽になった

2021/12/10に公開

この記事はQiita Advent Calendar Swiftの10日目の記事です。
カレンダーの予定が空いていたので急遽書いています。

はじめに

RxSwiftを利用するときsubscribe(onNext:)などでキャプチャリスト([weak self]等)をよく使うと思います。

RxSwift 6.0/6.1で導入されたオペレーターと拡張された引数を使うことでより簡単に弱参照なオブジェクトを扱うことができます。

RxSwift 6.0で追加されたwithUnretained(_:)

RxSwift 6.0(リリースノート)で新たにwithUnretained(_:)オペレーターが追加されました。

Add new withUnretained(_:) operator.

このオペレーターを使うと、引数で渡したオブジェクト弱参照のオブジェクトがタプルとして追加され、その後の別のオペレーターなどで利用できます。

let stream: Observable<String> = ...

// キャプチャリストを使う場合

stream
    .compactMap { [weak self] name in
        guard let self = self else { return nil }
        return "ID: \(self.id), NAME: \(name)"
    }

// 以下のように書ける

stream
    .withUnretained(self) // Observable<(Self, String)>
    .map { owner, name in
        "ID: \(owner.id), NAME: \(name)"
    }

RxSwift 6.1で変更されたsubscribe(with:)

RxSwift 6.1(リリースノート / PR)でsubscribeメソッドの引数にwithが追加されました。

Add new subscribe(with:onNext:onError:onCompleted:onDisposed:) alternatives to withUnretained. This exists for all traits and types: Observable, Driver, Signal, Infallible, Completable, Single, Maybe #2290

こちらの変更はObservableだけではなく、その他のTraitでも利用可能です。

let stream: Observable<String> = ...

// キャプチャリストを使う場合

stream
    .subscribe(
        onNext: { [weak self] name in
            guard let self = self else { return }
            print("ID: \(self.id), NAME: \(name)")
        },
        onError: { [weak self] error in
            guard let self = self else { return }
            self.errorMessage = "Error: \(error)"
        }
    )

// 以下のように書ける

stream
    .subscribe(
        with: self,
        onNext: { owner, name in // selfの弱参照オブジェクトが非Optional型で`owner`に入る
            print("ID: \(owner.id), NAME: \(name)")
        },
        onError: { owner, error in // ← `onError`など他のクロージャにも弱参照オブジェクトが渡ってくる
            owner.errorMessage = "Error: \(error)"
        }
    )

⚠️ 注意点

タプルが値として流れるObservableの場合、ネストしたタプルとして値が流れるので書き方を工夫する必要があります。

let stream: Observable<(String, String)> = ...

stream
    .subscribe(onNext: { [weak self] firstName, lastName in
        guard let self = self else { return }
        print("ID: \(self.id), NAME: \(firstName)-\(lastName)")
    })

stream
    .subscribe(with: self, onNext: { owner, values in // クロージャの引数の型は(Self, (String, String))となる
        // 直接使う
        print("ID: \(owner.id), NAME: \(values.0)-\(values.1)")

        // 今まで通り使うならタプルを分解する
        let (id, name) = values
        print("ID: \(owner.id), NAME: \(firstName)-\(lastName)")
    })

まとめ

新しいオペレーター・引数を利用することで、オプショナルを外す処理など本質ではない部分のコードが減ることでより処理の本質部分が分かりやすくなりましたね。

より見やすいコードが書けるので、積極的に使っていくのが良さそうです。

Discussion