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:
Hello
y 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
.finished
al asunto para que el operador .prepend
funcione):
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
Hello
y World
! enviado a la consola:
Similar a lo que usamos anteriormente
.prepend
para agregar otra Publisher
a, también tenemos esta opción para el operador .append
:
3.cambiar a la más reciente
Un operador más complejo
.switchToLatest
nos 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
PassthroughSubject
a los que enviaremos valores. - Creamos un objeto principal
PassthroughSubject
que despacha otros objetosPassthroughSubject
. - Enviamos
stringSubject1
al tema principal. stringSubject1
obtiene el valor A.- Enviamos
stringSubject2
al sujeto principal, descartando automáticamente los eventos stringSubject1. - Del mismo modo, le enviamos valores
stringSubject2
, nos conectamosstringSubject3
y le enviamos un evento de finalización.
El resultado es la salida
A
, C
, D
y G
:
Para simplificar, la función
isAvailable
devuelve un valor aleatorio Bool
despué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,
.switchToLatest
logramos lo que queremos. Solo se mostrará un valor bool:
4.merge (con :)
Usamos
.merge(with:)
para combinar dos Publishers
s 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
.combineLatest
publica 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
UITextFields
y 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 passwordTextField
reciba user12
, y en 12345678
consecuencia, se cumple la condición y se activa el botón:
6.zip
El operador
.zip
entrega 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
intSubject1
y intSubject2
:
- 0 y 4
- 1 y 1
- 6 y 7
Este último valor
9
no se muestra porque el intSubject1
valor 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: