Dans l'infrastructure Combine, un éditeur est un type conforme au protocole Publisher
. Il est chargé de fournir un flux de valeurs aux abonnés. Le protocole Publisher
définit deux types associés : Output
et Failure
, qui indiquent respectivement les types de valeurs que l'éditeur peut émettre et les types d'erreurs qu'il peut lancer.
Un éditeur peut émettre une ou plusieurs valeurs au fil du temps, et il peut également se terminer ou échouer. Lorsqu'un abonné s'abonne à un éditeur, l'éditeur appelle la méthode receive(subscription:)
de l'abonné et lui transmet un objet Subscription
, que l'abonné peut utiliser pour contrôler le flux de valeurs. L'abonné peut également appeler la méthode receive(_:)
sur l'éditeur pour recevoir de nouvelles valeurs.
Le framework Combine fournit un certain nombre d'éditeurs intégrés, tels que Just
, Fail
, Empty
, Deferred
et Sequence
, qui peuvent être utilisés pour créer différents types d'éditeurs. De plus, vous pouvez créer vos propres éditeurs personnalisés en vous conformant au protocole Publisher
et en implémentant les méthodes requises.
Les éditeurs peuvent également être composés ensemble pour créer des pipelines plus complexes. Le framework Combine fournit un certain nombre d'opérateurs intégrés qui peuvent être utilisés pour modifier et combiner des éditeurs, tels que map
, filter
, reduce
, flatMap
, zip
et merge
. Ces opérateurs sont fournis par l'extension de protocole Publisher
et peuvent être appelés sur n'importe quel éditeur.
Maintenant, je voudrais vous proposer quelques éditeurs utiles que j'utilise dans mes projets.
Pour implémenter un éditeur qui utilise un minuteur répétitif avec un intervalle personnalisé dans Swift, vous pouvez utiliser la classe Timer
du framework Foundation. Voici un exemple de la façon dont vous pouvez le faire :
RepeatingTimeSubscription
est conforme au protocole 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
est conforme au protocole 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) } }
Pour utiliser cet éditeur, vous pouvez en créer une instance et vous y abonner à l'aide de la sink
du récepteur du protocole Publisher
.
Par example:
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)
Cela affichera "Minuterie déclenchée !" toutes les 5 secondes. Vous pouvez annuler l'abonnement en appelant la méthode d' cancel
sur l'objet AnyCancellable
renvoyé par la méthode du sink
.
Par example:
deinit { cancellable?.cancel() }
Pour implémenter une interrogation longue à l'aide du framework Combine dans Swift, vous pouvez créer un éditeur qui effectue une requête réseau à un intervalle spécifié et renvoie la réponse en sortie. Voici un exemple de la façon dont vous pouvez le faire :
Énumération d'erreur personnalisée pour les cas ayant échoué.
enum CustomError: Error { case invalidResponse case invalidDecoding case error }
LongPollingSubscription
est conforme au protocole d' 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
est conforme au protocole 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>
signifie que vous pouvez utiliser n'importe quel type de réponse générique conforme au protocole Decodable
.
Pour les tests, vous devez créer un modèle conforme Decodable
. J'utilise une API publique par https://pixabay.com/api .
Soit la structure PhotoResponse :
struct PhotoResponse: Decodable { struct Photo: Decodable { let user: String let id: Int let largeImageURL: String } let hits: [Photo] let total: Int }
Pour utiliser cet éditeur, vous pouvez en créer une instance et vous y abonner à l'aide de la sink
du récepteur du protocole Publisher
. Par example:
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()
Il existe un certain nombre d'éditeurs personnalisés utiles qui peuvent être créés à l'aide du framework Combine dans Swift. Voici quelques exemples:
NotificationCenterPublisher
: un éditeur qui émet des valeurs lorsqu'une notification spécifiée est publiée sur le NotificationCenter
. Vous pouvez utiliser cet éditeur pour réagir à des événements spécifiques au système ou à l'application, tels qu'une rotation d'appareil ou un changement d'état du réseau.KeyboardPublisher
: Un éditeur qui émet des valeurs lorsque le clavier est affiché ou masqué. Vous pouvez utiliser cet éditeur pour ajuster la disposition de vos vues lorsque le clavier est présenté ou fermé.CoreLocationPublisher
: un éditeur qui émet des valeurs lorsque l'emplacement de l'utilisateur change. Cet éditeur peut être utilisé pour suivre l'emplacement de l'utilisateur et effectuer des actions en fonction de son emplacement.UIControlEventPublisher
: un éditeur qui émet des valeurs lorsqu'un événement spécifié se produit sur un UIControl
tel que UIButton ou UITextField. Cela peut être utilisé pour gérer les interactions des utilisateurs de manière réactive.Ce ne sont là que quelques exemples des types d'éditeurs personnalisés qui peuvent être créés à l'aide du framework Combine. La clé est de comprendre les exigences de votre application et d'utiliser les blocs de construction disponibles fournis par le framework pour créer des éditeurs qui répondent à ces exigences.
Enfin, il est important de noter que le framework Combine utilise le paradigme de la programmation réactive fonctionnelle, qui est un modèle de programmation qui traite des flux d'événements au fil du temps. Les éditeurs, ainsi que les abonnés et les opérateurs, sont les éléments de base de ce paradigme, et ils facilitent la création d'applications complexes et réactives.