In diesem Artikel werden sechs nützliche Kombinationsoperatoren vorgestellt. Wir werden dies anhand von Beispielen tun und mit jedem auf dem Xcode-Spielplatz experimentieren.
Der Quellcode ist am Ende des Artikels verfügbar.
Nun, ohne weiteres, fangen wir an.
1.vorbereiten
Mit dieser Gruppe von Anweisungen können wir Ereignisse, Werte oder andere Herausgeber zu unserem ursprünglichen Herausgeber hinzufügen (voranstellen):
import Foundation
import Combine
var subscriptions = Set<AnyCancellable>()
func prependOutputExample() {
let stringPublisher = ["World!"].publisher
stringPublisher
.prepend("Hello")
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
}
Ergebnis:
Hellound World! werden in sequentieller Reihenfolge ausgegeben:
Fügen wir nun einen weiteren Publisher des gleichen Typs hinzu:
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)
}
Das Ergebnis ähnelt dem vorherigen (beachten Sie, dass wir ein Ereignis
.finishedan den Betreff senden müssen, damit der Bediener .prependarbeiten kann):
2. anhängen
Der Operator
.append(wörtlich "zum Ende hinzufügen") funktioniert ähnlich .prepend, aber in diesem Fall fügen wir dem ursprünglichen Herausgeber Werte hinzu:
func appendOutputExample() {
let stringPublisher = ["Hello"].publisher
stringPublisher
.append("World!")
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
}
Als Ergebnis sehen wir
Hellound World! Ausgabe an die Konsole:
Ähnlich wie zuvor
.prepend, um ein weiteres Publishera hinzuzufügen , haben wir auch diese Option für den Operator .append:
3.switchToLatest
Ein komplexerer Operator
.switchToLatestermöglicht es uns, eine Reihe von Publishern zu einem Ereignisstrom zusammenzufassen:
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)
}
Folgendes ist im Code los:
- Wir erstellen drei Objekte,
PassthroughSubjectan die wir Werte senden. - Wir erstellen ein Hauptobjekt
PassthroughSubject, das andere Objekte versendetPassthroughSubject. - Wir versenden
stringSubject1zum Hauptthema. stringSubject1erhält den Wert A.- Wir versenden
stringSubject2zum Hauptthema und verwerfen automatisch stringSubject1-Ereignisse. - Ebenso senden wir Werte an ein Abschlussereignis
stringSubject2, stellen eine Verbindung zu diesem herstringSubject3und senden es an dieses aus.
Das Ergebnis wird
A, C, Dund G:
Der Einfachheit halber wird die Funktion
isAvailableeinen zufälligen Wert zurückgibt Boolnach einer gewissen Verzögerung.
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")
}
}
Dank des Betreibers
.switchToLatesterreichen wir, was wir wollen. Es wird nur ein Bool-Wert angezeigt:
4.merge (mit :)
Wir verwenden
.merge(with:)zwei Publisherss so, als würden wir Werte von nur einem erhalten:
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")
}
Das Ergebnis ist eine abwechselnde Folge von Elementen:
5.combineLatest
Der Bediener
.combineLatestveröffentlicht ein Tupel mit dem neuesten Wert jedes Herausgebers.
Betrachten Sie zur Veranschaulichung das folgende Beispiel aus der Praxis: Wir haben einen Benutzernamen, ein Kennwort
UITextFieldsund eine Schaltfläche zum Fortfahren. Wir möchten die Schaltfläche deaktiviert lassen, bis der Benutzername mindestens fünf Zeichen lang und das Kennwort mindestens acht Zeichen lang ist. Wir können dies leicht mit dem Operator erreichen .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")
}
Einmal
usernameTextField und passwordTextFieldempfangen user12, und 12345678dementsprechend ist die Bedingung erfüllt und die Schaltfläche wird aktiviert:
6.zip
Der Bediener
.zipliefert von jedem Herausgeber ein Paar übereinstimmender Werte. Angenommen, wir möchten feststellen, ob beide Herausgeber denselben Wert veröffentlicht haben 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) // ,
}
Wir haben die folgenden entsprechenden Werte von
intSubject1und intSubject2:
- 0 und 4
- 1 und 1
- 6 und 7
Letzterer Wert wird
9nicht angezeigt, da der intSubject1entsprechende Wert noch nicht veröffentlicht wurde:
Ressourcen
Der Quellcode ist bei Gist erhältlich .
Fazit
Interessiert an anderen Arten von Kombinationsoperatoren? Fühlen Sie sich frei, meine anderen Artikel zu besuchen: