Combineとは:基本概念、PublisherとSubscriber

著者: IT Sectr 公開日: 2026-03-17 読了時間: 8 分

CombineはAppleがiOS 13で導入したリアクティブプログラミングフレームワークです。Apple Documentation, 2025によると、Combineは非同期イベントを処理するための統一された宣言型APIを提供します。Publisherがデータソースを定義し、Subscriberが購読してデータを受信します。

重要なポイント

  • Combine — iOS 13以降で利用可能なAppleの組み込みリアクティブプログラミングフレームワーク
  • Publisher — Failureエラーの可能性を持つOutput値のソースを定義するプロトコル
  • Subscriber — デマンド管理とともにPublisherから値を受け取るプロトコル
  • Subject — 手動でイベントを注入できるPublisher(PassthroughSubject、CurrentValueSubject)
  • Operators — ストリームの変換と結合のためのmap、filter、combineLatest、flatMap

Combineとは?

Combineは、時間の経過とともに非同期イベントを処理するための宣言型Swiftフレームワークです。コールバックベースのアプローチとは異なり、Combineはオペレーターのチェーンを通じてデータ処理パイプラインを記述できます。フレームワークはFoundation(URLSession、Timer、NotificationCenter)およびSwiftUI(ObservableObject、@Published)と統合されています。

Publisherプロトコル

Publisherプロトコルを実装する型は、Output(値の型)とFailure(エラーの型)を定義する必要があります。Publisherは購読されるまでアクティブになりません — subscribeが呼び出された後にのみイベントの出力を開始します。これはリアクティブストリームに特徴的なコールドセマンティクスであり、効率を向上させます。

Subscriberプロトコル

Subscriberは3つのメソッドを通じてイベントを受け取ります:receive(subscription:) — デマンド管理による購読の確認、receive(_:) — 新しい値の受信、receive(completion:) — 成功またはエラーによるストリームの完了。DemandはSubscriberが受け入れる用意がある値の数を指定します — これがバックプレッシャー機構です。

組み込みPublisher

Appleは多くの組み込みPublisherを提供しています:Justは単一の値を出力して完了します。Futureはクロージャーによる非同期結果、Deferredは購読時までPublisherの作成を遅延します。URLSession.dataTaskPublisherはネットワークリクエストをリアクティブストリームに変換します。Timer.publishは定期的なタイマーを作成します。NotificationCenter.default.publisherは通知をPublisherに変換します。これらの統合により、手動のブリッジなしでイベントを購読できます。

CombineのPublisherとSubscriber

Combineでは、PublisherとSubscriberはSubscriptionプロトコルを介して接続されます。Subscriptionはデータフローを制御する接続オブジェクトです。開発者は独自のPublisher型を作成する必要はありません — Appleが組み込み型を提供しています:Just、Future、Deferred、Fail、Empty、およびURLSessionとTimer用のPublisher。

CombineのSubject

Subjectは手動で値を送信できるPublisherです。PassthroughSubjectは状態を保持しません。購読者は購読後に発生したイベントのみを受け取ります。CurrentValueSubjectはRxSwiftのBehaviorRelayと同様に、現在の値を保持し、新しい購読者にすぐに渡します。

バックプレッシャー

Combineのバックプレッシャー機構はデータ転送速度を調整します。Subscriberはsubscription.request(.unlimited)またはsubscription.request(.max(N))を通じて、処理する準備ができている値の数をPublisherに伝えます。これにより、プロデューサーとコンシューマーの速度が不均等な場合のバッファオーバーフローを防ぎます。

Combineのオペレーター

Combineには100以上のオペレーターがあり、カテゴリに分かれています:変換(map、tryMap、flatMap)、フィルタリング(filter、compactMap、removeDuplicates)、結合(combineLatest、merge、zip)、時間管理(debounce、throttle、delay)、エラー処理(catch、replaceError、retry)。

combineLatestオペレーターは2つのPublisherの最新値を結合します — どちらかが新しい値を出力するたびに、両方の更新された値でクロージャーが呼び出されます。このオペレーターは、複数の入力フィールドの状態を同時に追跡する必要があるフォーム検証に不可欠です。

debounceオペレーターは、新しいイベントなしで指定された間隔が経過するまで値の公開を遅延します。これは検索フィールドにとって重要です。ユーザーが300〜500ミリ秒間入力を停止した後にのみサーバーリクエストが送信され、負荷が5〜10倍削減されます。

エラー処理

Combineはオペレーターレベルでいくつかのエラー処理戦略を提供します。catchはFailureをインターセプトし、Publisherをフォールバックに置き換えます — ストリームはクラッシュせずに続行します。replaceError(with:)はエラーの代わりにデフォルト値を提供します。retry(_:)は失敗時に指定された回数だけ購読を再試行します。重要なのは、PublisherシグネチャのNeverエラー型はストリームがエラーで終了しないことを保証します — これにより、Failure処理なしでSwiftUIでPublisherを使用できます。

Combineコード例

最初の例は、Justの基本的な使用法を示しています — 単一の値を出力して完了するPublisherです:

swift
let publisher = Just("Hello Combine")

publisher
    .sink(receiveCompletion: { completion in
        print("完了: \(completion)")
    }, receiveValue: { value in
        print("受信: \(value)")
    })

2番目の例は、メインスレッドでの結果処理を伴うネットワークリクエストのためのURLSessionとのCombine統合を示しています:

swift
let url = URL(string: "https://api.example.com")!

URLSession.shared
    .dataTaskPublisher(for: url)
    .map { $0.data }
    .receive(on: DispatchQueue.main)
    .sink(receiveCompletion: { completion in
        if case .failure(let error) = completion {
            print("エラー: \(error)")
        }
    }, receiveValue: { data in
        print("取得したデータ: \(data.count)")
    })

3番目の例は、状態管理のためのCurrentValueSubjectの使用法を示しています:

swift
let counter = CurrentValueSubject<Int, Never>(0)

counter
    .sink { value in
        print("カウンタ: \(value)")
    }

counter.send(1) // 出力: カウンタ: 1
counter.send(2) // 出力: カウンタ: 2
print(counter.value) // 出力: 2

Combine vs RxSwift

CombineとRxSwiftは同じ問題 — リアクティブプログラミング — を解決しますが、アプローチが異なります。CombineはAppleのネイティブフレームワークでシステムに組み込まれています:依存関係のインストール不要、SwiftUIおよびFoundationと統合済み。RxSwiftはiOS 9+をサポートするサードパーティライブラリで、より幅広いオペレーターセットを持っています。

ネイティブC実装のおかげで、一般的なシナリオではCombineのパフォーマンスが高くなります。RxSwiftはより多くのデバッグツール(RxSwift.Resources.total、Debug)を提供しますが、オブジェクト指向アーキテクチャは大規模なデータストリームでは効率が低下する可能性があります。両者の選択は、機能と統合のトレードオフです。

重要な違いはバックプレッシャーモデルです。Combineでは、SubscriberがSubscriptionを通じてデマンドを管理し、ストリーム速度を明示的に制御します。RxSwiftでは、バックプレッシャーはデフォルトで制御されません — Observableはすべての値を出力し、Subscriberは到着次第処理します。大規模なデータストリームでは、Combineの方が予測可能でメモリ安全です。

Combineは言語レベルでSwiftUIと深く統合されています。@PublishedはPublisherを生成し、ObservableObjectはobjectWillChange Publisherを提供します。SwiftUI Viewは自動的にこれらのPublisherを購読し、変更時に再描画します。UIKit用にはCombineCocoaのようなサードパーティラッパーがありますが、Combineは元々SwiftUI用に設計されたため、UIKitでの統合には追加のコードが必要です。一方、RxSwiftはUIKit向けの豊富なエコシステムを持っています:RxCocoaはすべてのUI要素にリアクティブラッパーを提供します。

CombineはCore Dataとの統合をNSFetchedResultsController Publisherを通じてサポートしています。PublisherはCore Dataスタックの変更(レコードの追加、削除、更新)のたびに新しいデータを出力します。これにより、手動でreloadDataを呼び出すことなくリアクティブリストを構築できます。CombineをUIApplication.willResignActiveNotification用のNotificationCenter Publisherと組み合わせることで、開発者はデリゲートやコールバックなしでアプリケーションライフサイクル全体のリアクティブレイヤーを作成します。ObservableObjectクラスの場合、@Publishedプロパティが変更されるとSwiftUIは自動的にViewを更新します — Combineは手動コードを一行も書かずにチェーン全体を処理します。Combineコードのユニットテストは、ImmediateSchedulerを介したScheduler分離を使用して、テストケースでXCTestExpectationとPublisher.sinkを通じて行われます。

UIKitプロジェクトでCombineを扱う場合、開発者はしばしばsinkを介して購読し、AnyCancellableをSetに格納します。典型的なパターン:ViewControllerがビューを作成し、cancellablesを格納し、ViewModelからPublisherを購読します。deinit時に、すべての購読はCancellablesコレクションを通じて自動的にキャンセルされます。CombineでのUIControlの適切な処理には、publisher(for:)拡張機能を介してUIControl.Event Publisherを使用できます。

Combineのデバッグはprint()またはhandleEvents()オペレーターを使用して行います。print(String)はすべてのイベントをログに記録します:receive subscription、request demand、receive value、receive completion。handleEventsはより細かい制御を提供します:Publisherライフサイクルの各段階でクロージャーを指定できます。テスト用にはCombineExpectationsがあります — 仮想時間とデマンド制御を使用して、Publisherが期待される値を出力するかどうかをテストで確認できるライブラリです。

Combineの実践

CombineはSwiftUIでのフォーム管理に積極的に使用されています:各@PublishedプロパティがPublisherを生成し、SwiftUIはView.bodyを介して購読します。combineLatestを介して複数のPublisherを組み合わせることで、フォーム検証が宣言的になります:各Publisherが1つのフィールドを追跡し、combineLatestがすべての値を収集し、mapが検証してSubmitボタンのステータスを返します。Publishers.Mergeは同じ型の複数のPublisherを1つに結合します — 異なるコントロールからのイベントを単一のストリームに収集するのに便利です。

CombineをUIKitと統合するには、拡張機能を介してUIControl Publisherを使用します:button.publisher(for: .touchUpInside)は、タップ時にイベントを出力するPublisherを返します。CombineCocoaは、すべてのUIControlイベント、UITextView.text、UIScrollView.contentOffset用のPublisherを提供するコミュニティライブラリです。購読解除時に、cancellablesストアはdeinitでクリアされ、どの購読も所有者より長生きしないことを保証します。複数の状態を持つUIControlの場合は、Publishers.MergeManyを使用してPublisherを配列に結合します。

よくある質問

CombineはどのiOSバージョンから利用可能ですか?

CombineはiOS 13、macOS 10.15、tvOS 13、watchOS 6から利用可能です。これにより、古いバージョンをサポートする必要があるプロジェクトでの使用が制限されます。iOS 12以下では、RxSwiftまたはサードパーティライブラリが使用されます。

PassthroughSubjectとCurrentValueSubjectの違いは?

PassthroughSubjectは状態を保持せず、新しい購読者に最後の値を再送しません — 将来のイベントのみを受け取ります。CurrentValueSubjectは現在の値を保持し、新しい購読者にすぐに渡します。CurrentValueSubject.valueを使用すると、現在の値を同期的に読み書きできます。

CombineはSwiftUIとどのように統合しますか?

CombineはSwiftUIの中核です:@PublishedがPublisherを生成し、ObservableObjectはobjectWillChange Publisherを使用します。SwiftUI ViewはCombineパイプラインを介して@ObservedObjectと@StateObjectに自動的に購読し、プロパティが変更されるとViewを再描画します。

Combineのバックプレッシャーとは?

バックプレッシャーはデータ転送速度を制御するための機構です。SubscriberはSubscriptionに対して、処理する準備ができている値の数(デマンド)を伝えます。Publisherは要求以上の値を出力できません。これはネットワークソケットからの大規模なデータストリームを扱う際に重要です。

Combineでエラーを処理するには?

catchオペレーターはエラーをインターセプトし、Publisherをフォールバックに置き換えます。replaceErrorはエラーをデフォルト値に置き換えます。retryはエラー時に指定された回数だけ購読を再試行します。すべてのオペレーターは、Failure時のクラッシュを防ぐためにsubscribeの前に配置する必要があります。

まとめ

  • Combine — iOS 13以降のリアクティブプログラミングのためのAppleの組み込みフレームワーク
  • Publisher — OutputとFailureを持つプロトコル、購読後に動作を開始
  • Subscriber — デマンド管理サポート付きでreceiveを介して値を受信
  • Subject(PassthroughSubject、CurrentValueSubject)— 手動イベント注入が可能なPublisher
  • オペレーター map、filter、combineLatest、debounce、catch — パイプラインの基盤
  • バックプレッシャーはデマンドを介してSubscriberのバッファオーバーフローを防止
  • iOS 13+を対象とする新規プロジェクトにはCombine、古いバージョンのサポートにはRxSwiftを選択

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください