paint-brush
Swift で役立つパブリッシャー: エッセンシャル ガイド@vadimchistiakov
1,389 測定値
1,389 測定値

Swift で役立つパブリッシャー: エッセンシャル ガイド

Vadim Chistiakov11m2023/01/18
Read on Terminal Reader

長すぎる; 読むには

パブリッシャーは、`Publisher` プロトコルに準拠するタイプです。サブスクライバーに値のストリームを提供する責任があります。パブリッシャーは、時間の経過とともに 1 つ以上の値を発行でき、完了または失敗することもあります。 Combine フレームワークには、多数の組み込みパブリッシャーが用意されています。
featured image - Swift で役立つパブリッシャー: エッセンシャル ガイド
Vadim Chistiakov HackerNoon profile picture
0-item

概要

結合フレームワークでは、パブリッシャーはPublisherプロトコルに準拠する型です。サブスクライバーに値のストリームを提供する責任があります。 Publisherプロトコルは、2 つの関連する型を定義します。 OutputFailureです。これらは、それぞれ発行者が出力できる値の型とスローできるエラーの型を示します。


パブリッシャーは、時間の経過とともに 1 つ以上の値を発行でき、完了または失敗することもあります。サブスクライバーがパブリッシャーにサブスクライブすると、パブリッシャーはサブスクライバーのreceive(subscription:)メソッドを呼び出して、サブスクライバーが値の流れを制御するために使用できるSubscriptionオブジェクトを渡します。サブスクライバーは、パブリッシャーでreceive(_:)メソッドを呼び出して、新しい値を受け取ることもできます。


Combine フレームワークには、 JustFailEmptyDeferredSequenceなど、さまざまな種類のパブリッシャーを作成するために使用できる組み込みパブリッシャーが多数用意されています。さらに、 Publisherプロトコルに準拠し、必要なメソッドを実装することで、独自のカスタム パブリッシャーを作成できます。


パブリッシャーを組み合わせて、より複雑なパイプラインを作成することもできます。 Combine フレームワークは、 mapfilterreduceflatMapzip 、およびmergeなど、パブリッシャーを変更および結合するために使用できる多数の組み込み演算子を提供します。これらの演算子はPublisherプロトコル拡張によって提供され、任意の発行元で呼び出すことができます。


ここで、私がプロジェクトで使用している便利なパブリッシャーをいくつか紹介したいと思います。

繰り返しタイマー パブリッシャー

Swift でカスタム間隔で繰り返しタイマーを使用するパブリッシャーを実装するには、Foundation フレームワークのTimerクラスを使用できます。これを行う方法の例を次に示します。


RepeatingTimeSubscriptionSubscriptionプロトコルに準拠しています。


 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 } }


RepeatingTimePublisherPublisherプロトコルに準拠しています。


 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 フレームワークを使用して作成できる便利なカスタム パブリッシャーが多数あります。以下にいくつかの例を示します。

  1. NotificationCenterPublisher : 指定された通知がNotificationCenterに投稿されたときに値を発行するパブリッシャー。このパブリッシャーを使用して、デバイスのローテーションやネットワーク ステータスの変化など、システムまたはアプリ固有のイベントに対応できます。
  2. KeyboardPublisher : キーボードが表示または非表示になったときに値を発行するパブリッシャー。このパブリッシャーを使用して、キーボードが表示または非表示になったときのビューのレイアウトを調整できます。
  3. CoreLocationPublisher : ユーザーの場所が変更されたときに値を発行するパブリッシャー。このパブリッシャーを使用して、ユーザーの場所を追跡し、場所に基づいてアクションを実行できます。
  4. UIControlEventPublisher : UIButton や UITextField などのUIControlで指定されたイベントが発生したときに値を発行するパブリッシャー。これは、リアクティブな方法でユーザー インタラクションを処理するために使用できます。

これらは、Combine フレームワークを使用して作成できるカスタム パブリッシャーのタイプのほんの一例です。重要なのは、アプリケーションの要件を理解し、フレームワークによって提供される利用可能なビルディング ブロックを使用して、それらの要件を満たすパブリッシャーを作成することです。

結論は

最後に、Combine フレームワークが関数型リアクティブ プログラミング パラダイムを使用していることに注意することが重要です。これは、時間の経過に伴うイベント ストリームを処理するプログラミング モデルです。パブリッシャーは、サブスクライバーやオペレーターと共に、このパラダイムのコア ビルディング ブロックであり、複雑で応答性の高いアプリケーションの作成を容易にします。