Dans cet article, nous examinerons six opérateurs Combine utiles. Nous allons le faire avec des exemples, en expérimentant chacun dans le Xcode Playground.
Le code source est disponible à la fin de l'article.
Eh bien, sans plus tarder, commençons.
1. préparer
Ce groupe de déclarations nous permet de préfixer (littéralement «ajouter») des événements, des valeurs ou d'autres éditeurs à notre éditeur d'origine:
import Foundation
import Combine
var subscriptions = Set<AnyCancellable>()
func prependOutputExample() {
let stringPublisher = ["World!"].publisher
stringPublisher
.prepend("Hello")
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
}
Résultat:
Hello
et World
! sont affichés dans un ordre séquentiel:
ajoutons maintenant un autre éditeur du même type:
func prependPublisherExample() {
let subject = PassthroughSubject<String, Never>()
let stringPublisher = ["Break things!"].publisher
stringPublisher
.prepend(subject)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
subject.send("Run code")
subject.send(completion: .finished)
}
Le résultat est similaire au précédent (notez que nous devons envoyer un événement
.finished
au sujet pour que l'opérateur .prepend
fonctionne):
2. ajouter
L'opérateur
.append
(littéralement "ajouter à la fin") fonctionne de la même manière .prepend
, mais dans ce cas, nous ajoutons des valeurs à l'éditeur d'origine:
func appendOutputExample() {
let stringPublisher = ["Hello"].publisher
stringPublisher
.append("World!")
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
}
En conséquence, nous voyons
Hello
et World
! sortie sur la console:
similaire à ce que nous avons utilisé précédemment
.prepend
pour ajouter un autre Publisher
a, nous avons également cette option pour l'opérateur .append
:
3. commutateurToLatest
Un opérateur plus complexe
.switchToLatest
nous permet de combiner une série d'éditeurs en un seul flux d'événements:
func switchToLatestExample() {
let stringSubject1 = PassthroughSubject<String, Never>()
let stringSubject2 = PassthroughSubject<String, Never>()
let stringSubject3 = PassthroughSubject<String, Never>()
let subjects = PassthroughSubject<PassthroughSubject<String, Never>, Never>()
subjects
.switchToLatest()
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
subjects.send(stringSubject1)
stringSubject1.send("A")
subjects.send(stringSubject2)
stringSubject1.send("B") //
stringSubject2.send("C")
stringSubject2.send("D")
subjects.send(stringSubject3)
stringSubject2.send("E") //
stringSubject2.send("F") //
stringSubject3.send("G")
stringSubject3.send(completion: .finished)
}
Voici ce qui se passe dans le code:
- Nous créons trois objets
PassthroughSubject
auxquels nous enverrons des valeurs. - Nous créons un objet principal
PassthroughSubject
qui distribue d'autres objetsPassthroughSubject
. - Nous expédions
stringSubject1
au sujet principal. stringSubject1
obtient la valeur A.- Nous
stringSubject2
envoyons au sujet principal, en supprimant automatiquement les événements stringSubject1. - De même, nous lui envoyons des valeurs
stringSubject2
, nous y connectonsstringSubject3
et lui envoyons un événement d'achèvement.
Le résultat est sortie
A
, C
, D
et G
:
Pour simplifier, la fonction
isAvailable
retourne une valeur aléatoire Bool
après un certain délai.
func switchToLatestExample2() {
func isAvailable(query: String) -> Future<Bool, Never> {
return Future { promise in
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
promise(.success(Bool.random()))
}
}
}
let searchSubject = PassthroughSubject<String, Never>()
searchSubject
.print("subject")
.map { isAvailable(query: $0) }
.print("search")
.switchToLatest()
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
searchSubject.send("Query 1")
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
searchSubject.send( "Query 2")
}
}
Grâce à l'opérateur,
.switchToLatest
nous réalisons ce que nous voulons. Une seule valeur booléenne sera affichée:
4. fusionner (avec :)
Nous utilisons
.merge(with:)
pour combiner deux Publishers
s comme si nous obtenions des valeurs à partir d'un seul:
func mergeWithExample() {
let stringSubject1 = PassthroughSubject<String, Never>()
let stringSubject2 = PassthroughSubject<String, Never>()
stringSubject1
.merge(with: stringSubject2)
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
stringSubject1.send("A")
stringSubject2.send("B")
stringSubject2.send("C")
stringSubject1.send("D")
}
Le résultat est une séquence alternée d'éléments:
5.combineDernières
L'opérateur
.combineLatest
publie un tuple contenant la dernière valeur de chaque éditeur.
Pour illustrer cela, considérons l'exemple réel suivant: nous avons un nom d'utilisateur, un mot de passe
UITextFields
et un bouton Continuer. Nous voulons garder le bouton désactivé jusqu'à ce que le nom d'utilisateur soit d'au moins cinq caractères et que le mot de passe soit d'au moins huit caractères. Nous pouvons facilement y parvenir en utilisant l'opérateur .combineLatest
:
func combineLatestExample() {
let usernameTextField = CurrentValueSubject<String, Never>("")
let passwordTextField = CurrentValueSubject<String, Never>("")
let isButtonEnabled = CurrentValueSubject<Bool, Never>(false)
usernameTextField
.combineLatest(passwordTextField)
.handleEvents(receiveOutput: { (username, password) in
print("Username: \(username), password: \(password)")
let isSatisfied = username.count >= 5 && password.count >= 8
isButtonEnabled.send(isSatisfied)
})
.sink(receiveValue: { _ in })
.store(in: &subscriptions)
isButtonEnabled
.sink { print("isButtonEnabled: \($0)") }
.store(in: &subscriptions)
usernameTextField.send("user")
usernameTextField.send("user12")
passwordTextField.send("12")
passwordTextField.send("12345678")
}
Une fois
usernameTextField
et passwordTextField
recevoir user12
, et en 12345678
conséquence, la condition est satisfaite, et le bouton est activé:
6. zip
L'opérateur
.zip
fournit une paire de valeurs correspondantes de chaque éditeur. Disons que nous voulons déterminer si les deux éditeurs ont publié la même valeur Int
:
func zipExample() {
let intSubject1 = PassthroughSubject<Int, Never>()
let intSubject2 = PassthroughSubject<Int, Never>()
let foundIdenticalPairSubject = PassthroughSubject<Bool, Never>()
intSubject1
.zip(intSubject2)
.handleEvents(receiveOutput: { (value1, value2) in
print("value1: \(value1), value2: \(value2)")
let isIdentical = value1 == value2
foundIdenticalPairSubject.send(isIdentical)
})
.sink(receiveValue: { _ in })
.store(in: &subscriptions)
foundIdenticalPairSubject
.sink(receiveValue: { print("is identical: \($0)") })
.store(in: &subscriptions)
intSubject1.send(0)
intSubject1.send(1)
intSubject2.send(4)
intSubject1.send(6)
intSubject2.send(1)
intSubject2.send(7)
intSubject2.send(9) // ,
}
Nous avons les valeurs correspondantes suivantes de
intSubject1
et intSubject2
:
- 0 et 4
- 1 et 1
- 6 et 7
Cette dernière valeur n'est
9
pas affichée car la intSubject1
valeur correspondante n'a pas encore été publiée:
Ressources
Le code source est disponible sur le Gist .
Conclusion
Intéressé par d'autres types d'opérateurs de moissonneuse-batteuse? N'hésitez pas à visiter mes autres articles: