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:
Helloet 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
.finishedau sujet pour que l'opérateur .prependfonctionne):
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
Helloet World! sortie sur la console:
similaire à ce que nous avons utilisé précédemment
.prependpour ajouter un autre Publishera, nous avons également cette option pour l'opérateur .append:
3. commutateurToLatest
Un opérateur plus complexe
.switchToLatestnous 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
PassthroughSubjectauxquels nous enverrons des valeurs. - Nous créons un objet principal
PassthroughSubjectqui distribue d'autres objetsPassthroughSubject. - Nous expédions
stringSubject1au sujet principal. stringSubject1obtient la valeur A.- Nous
stringSubject2envoyons au sujet principal, en supprimant automatiquement les événements stringSubject1. - De même, nous lui envoyons des valeurs
stringSubject2, nous y connectonsstringSubject3et lui envoyons un événement d'achèvement.
Le résultat est sortie
A, C, Det G:
Pour simplifier, la fonction
isAvailableretourne une valeur aléatoire Boolaprè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,
.switchToLatestnous réalisons ce que nous voulons. Une seule valeur booléenne sera affichée:
4. fusionner (avec :)
Nous utilisons
.merge(with:)pour combiner deux Publisherss 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
.combineLatestpublie 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
UITextFieldset 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 passwordTextFieldrecevoir user12, et en 12345678conséquence, la condition est satisfaite, et le bouton est activé:
6. zip
L'opérateur
.zipfournit 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
intSubject1et intSubject2:
- 0 et 4
- 1 et 1
- 6 et 7
Cette dernière valeur n'est
9pas affichée car la intSubject1valeur 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: