En este artículo, veremos seis operadores de combinación útiles. Haremos esto con ejemplos, experimentando con cada uno en Xcode Playground.
El código fuente está disponible al final del artículo.
Bueno, sin más preámbulos, comencemos.
1.prepend
Este grupo de declaraciones nos permite anteponer (literalmente "anteponer") eventos, valores u otros editores a nuestro editor original:
import Foundation
import Combine
var subscriptions = Set<AnyCancellable>()
func prependOutputExample() {
let stringPublisher = ["World!"].publisher
stringPublisher
.prepend("Hello")
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
}
Resultado:
Helloy World! se generan en orden secuencial:
ahora agreguemos otro editor del mismo tipo:
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)
}
El resultado es similar al anterior (tenga en cuenta que necesitamos enviar un evento
.finishedal asunto para que el operador .prependfuncione):
2. añadir
El operador
.append(literalmente "agregar al final") funciona de manera similar .prepend, pero en este caso agregamos valores al editor original:
func appendOutputExample() {
let stringPublisher = ["Hello"].publisher
stringPublisher
.append("World!")
.sink(receiveValue: { print($0) })
.store(in: &subscriptions)
}
Como resultado, vemos
Helloy World! enviado a la consola:
Similar a lo que usamos anteriormente
.prependpara agregar otra Publishera, también tenemos esta opción para el operador .append:
3.cambiar a la más reciente
Un operador más complejo
.switchToLatestnos permite combinar una serie de editores en una secuencia de eventos:
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)
}
Esto es lo que sucede en el código:
- Creamos tres objetos
PassthroughSubjecta los que enviaremos valores. - Creamos un objeto principal
PassthroughSubjectque despacha otros objetosPassthroughSubject. - Enviamos
stringSubject1al tema principal. stringSubject1obtiene el valor A.- Enviamos
stringSubject2al sujeto principal, descartando automáticamente los eventos stringSubject1. - Del mismo modo, le enviamos valores
stringSubject2, nos conectamosstringSubject3y le enviamos un evento de finalización.
El resultado es la salida
A, C, Dy G:
Para simplificar, la función
isAvailabledevuelve un valor aleatorio Booldespués de un cierto retraso.
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")
}
}
Gracias al operador,
.switchToLatestlogramos lo que queremos. Solo se mostrará un valor bool:
4.merge (con :)
Usamos
.merge(with:)para combinar dos Publisherss como si estuviéramos obteniendo valores de solo uno:
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")
}
El resultado es una secuencia alterna de elementos:
5.combineLatest
El operador
.combineLatestpublica una tupla que contiene el último valor de cada editor.
Para ilustrar esto, considere el siguiente ejemplo del mundo real: tenemos un nombre de usuario, contraseña
UITextFieldsy un botón de continuar. Queremos mantener el botón desactivado hasta que el nombre de usuario tenga al menos cinco caracteres y la contraseña tenga al menos ocho caracteres. Podemos lograr esto fácilmente usando el operador .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")
}
Una vez
usernameTextField y passwordTextFieldreciba user12, y en 12345678consecuencia, se cumple la condición y se activa el botón:
6.zip
El operador
.zipentrega un par de valores coincidentes de cada editor. Digamos que queremos determinar si ambos editores han publicado el mismo valor 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) // ,
}
Tenemos los siguientes valores correspondientes de
intSubject1y intSubject2:
- 0 y 4
- 1 y 1
- 6 y 7
Este último valor
9no se muestra porque el intSubject1valor correspondiente aún no se ha publicado:
Recursos
El código fuente está disponible en Gist .
Conclusión
¿Interesado en otros tipos de operadores de combinación? No dude en visitar mis otros artículos: