iOS team lead, developer. 6 years experience of specializing in mobile development. Super nerd who loves Apple and code
This story contains new, firsthand information uncovered by the writer.
結合フレームワークでは、パブリッシャーはPublisher
プロトコルに準拠する型です。サブスクライバーに値のストリームを提供する責任があります。 Publisher
プロトコルは、2 つの関連する型を定義します。 Output
とFailure
です。これらは、それぞれ発行者が出力できる値の型とスローできるエラーの型を示します。
パブリッシャーは、時間の経過とともに 1 つ以上の値を発行でき、完了または失敗することもあります。サブスクライバーがパブリッシャーにサブスクライブすると、パブリッシャーはサブスクライバーのreceive(subscription:)
メソッドを呼び出して、サブスクライバーが値の流れを制御するために使用できるSubscription
オブジェクトを渡します。サブスクライバーは、パブリッシャーでreceive(_:)
メソッドを呼び出して、新しい値を受け取ることもできます。
Combine フレームワークには、 Just
、 Fail
、 Empty
、 Deferred
、 Sequence
など、さまざまな種類のパブリッシャーを作成するために使用できる組み込みパブリッシャーが多数用意されています。さらに、 Publisher
プロトコルに準拠し、必要なメソッドを実装することで、独自のカスタム パブリッシャーを作成できます。
パブリッシャーを組み合わせて、より複雑なパイプラインを作成することもできます。 Combine フレームワークは、 map
、 filter
、 reduce
、 flatMap
、 zip
、およびmerge
など、パブリッシャーを変更および結合するために使用できる多数の組み込み演算子を提供します。これらの演算子はPublisher
プロトコル拡張によって提供され、任意の発行元で呼び出すことができます。
ここで、私がプロジェクトで使用している便利なパブリッシャーをいくつか紹介したいと思います。
Swift でカスタム間隔で繰り返しタイマーを使用するパブリッシャーを実装するには、Foundation フレームワークのTimer
クラスを使用できます。これを行う方法の例を次に示します。
RepeatingTimeSubscription
はSubscription
プロトコルに準拠しています。
private class RepeatingTimerSubscription<S: Subscriber>: Subscription where S.Input == Void { private let interval: TimeInterval private let queue: DispatchQueue private var subscriber: S? private var timer: Timer? init(interval: TimeInterval, queue: DispatchQueue, subscriber: S) { self.interval = interval self.queue = queue self.subscriber = subscriber } func request(_ demand: Subscribers.Demand) { timer?.invalidate() timer = Timer.scheduledTimer( withTimeInterval: interval, repeats: true ) { [weak self] _ in self?.queue.async { _ = self?.subscriber?.receive() } } } func cancel() { timer?.invalidate() timer = nil subscriber = nil } }
RepeatingTimePublisher
はPublisher
プロトコルに準拠しています。
import Foundation import Combine final class RepeatingTimerPublisher: Publisher { typealias Output = Void typealias Failure = Never private let interval: TimeInterval private let queue: DispatchQueue init(interval: TimeInterval, queue: DispatchQueue = .main) { self.interval = interval self.queue = queue } func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input { let subscription = RepeatingTimerSubscription( interval: interval, queue: queue, subscriber: subscriber ) subscriber.receive(subscription: subscription) } }
このパブリッシャーを使用するには、そのインスタンスを作成し、 Publisher
プロトコルのsink
メソッドを使用してサブスクライブできます。
例えば:
private var cancellable: AnyCancellable? func subscribeOnTimer(interval: TimeInterval) { let publisher = RepeatingTimerPublisher(interval: interval) cancellable = publisher.sink { print("Timer fired!") } } //TEST THE METHOD subscribeOnTimer(interval: 5.0)
これにより、「タイマーが作動しました!」と出力されます。 5秒ごと。 sink
メソッドによって返されるAnyCancellable
オブジェクトでcancel
メソッドを呼び出すことにより、サブスクリプションをキャンセルできます。
例えば:
deinit { cancellable?.cancel() }
Swift で結合フレームワークを使用してロング ポーリングを実装するには、指定された間隔でネットワーク リクエストを行い、応答を出力として返すパブリッシャーを作成できます。これを行う方法の例を次に示します。
失敗したケースのカスタム エラー列挙。
enum CustomError: Error { case invalidResponse case invalidDecoding case error }
LongPollingSubscription
Subscription
プロトコルに準拠しています。
private class LongPollingSubscription<S: Subscriber, Output: Decodable>: Subscription where S.Input == Output, S.Failure == CustomError { private let url: URL private let interval: TimeInterval private let decoder: JSONDecoder private var subscriber: S? private var timer: Timer? private var task: URLSessionDataTask? init( url: URL, interval: TimeInterval, subscriber: S, decoder: JSONDecoder = JSONDecoder() ) { self.url = url self.interval = interval self.subscriber = subscriber self.decoder = decoder } func request(_ demand: Subscribers.Demand) { timer?.invalidate() timer = Timer.scheduledTimer( withTimeInterval: interval, repeats: true ) { [weak self] _ in self?.makeRequest() } makeRequest() } func cancel() { timer?.invalidate() timer = nil task?.cancel() task = nil subscriber = nil } private func makeRequest() { task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in guard let self else { return } if let error = error as? S.Failure { self.subscriber?.receive( completion: .failure(error) ) return } guard let data else { self.subscriber?.receive( completion: .failure(.invalidResponse) ) return } do { let output = try self.decoder.decode( Output.self, from: data ) _ = self.subscriber?.receive(output) } catch { self.subscriber?.receive( completion: .failure(.invalidDecoding) ) } } task?.resume() } }
LongPollingPublisher
Publisher
プロトコルに準拠しています。
final class LongPollingPublisher<Output: Decodable>: Publisher { typealias Failure = CustomError private let url: URL private let interval: TimeInterval init(url: URL, interval: TimeInterval) { self.url = url self.interval = interval } func receive<S>(subscriber: S) where S: Subscriber, Failure == S.Failure, Output == S.Input { let subscription = LongPollingSubscription( url: url, interval: interval, subscriber: subscriber ) subscriber.receive(subscription: subscription) } }
<Output: Decodable>
は、 Decodable
プロトコルに準拠する任意の汎用タイプの応答を使用できることを意味します。
テストのために、 Decodable
に準拠するモデルを作成する必要があります。 https://pixabay.com/apiの公開 API を使用しています。
それを PhotoResponse 構造体にしましょう:
struct PhotoResponse: Decodable { struct Photo: Decodable { let user: String let id: Int let largeImageURL: String } let hits: [Photo] let total: Int }
このパブリッシャーを使用するには、そのインスタンスを作成し、 Publisher
プロトコルのsink
メソッドを使用してサブスクライブできます。例えば:
private var cancellable: AnyCancellable? private func pollingTest() { let url = URL(string: "https://pixabay.com/api/?key={your_key}")! let publisher = LongPollingPublisher<PhotoResponse>( url: url, interval: 5.0 ) cancellable = publisher.sink(receiveCompletion: { completion in switch completion { case .finished: print("Completed") case .failure(let error): print("Error: \(error)") } }, receiveValue: { response in print("Received response: \(response)") }) } //TEST THE METHOD pollingTest()
Swift の Combine フレームワークを使用して作成できる便利なカスタム パブリッシャーが多数あります。以下にいくつかの例を示します。
NotificationCenterPublisher
: 指定された通知がNotificationCenter
に投稿されたときに値を発行するパブリッシャー。このパブリッシャーを使用して、デバイスのローテーションやネットワーク ステータスの変化など、システムまたはアプリ固有のイベントに対応できます。KeyboardPublisher
: キーボードが表示または非表示になったときに値を発行するパブリッシャー。このパブリッシャーを使用して、キーボードが表示または非表示になったときのビューのレイアウトを調整できます。CoreLocationPublisher
: ユーザーの場所が変更されたときに値を発行するパブリッシャー。このパブリッシャーを使用して、ユーザーの場所を追跡し、場所に基づいてアクションを実行できます。UIControlEventPublisher
: UIButton や UITextField などのUIControl
で指定されたイベントが発生したときに値を発行するパブリッシャー。これは、リアクティブな方法でユーザー インタラクションを処理するために使用できます。これらは、Combine フレームワークを使用して作成できるカスタム パブリッシャーのタイプのほんの一例です。重要なのは、アプリケーションの要件を理解し、フレームワークによって提供される利用可能なビルディング ブロックを使用して、それらの要件を満たすパブリッシャーを作成することです。
最後に、Combine フレームワークが関数型リアクティブ プログラミング パラダイムを使用していることに注意することが重要です。これは、時間の経過に伴うイベント ストリームを処理するプログラミング モデルです。パブリッシャーは、サブスクライバーやオペレーターと共に、このパラダイムのコア ビルディング ブロックであり、複雑で応答性の高いアプリケーションの作成を容易にします。