6 operadores de cosechadoras rápidas que debe conocer

La traducción del artículo se preparó antes del inicio del curso avanzado "Desarrollador iOS".








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 objetos PassthroughSubject.
  • Enviamos stringSubject1al tema principal.
  • stringSubject1 obtiene el valor A.
  • Enviamos stringSubject2al sujeto principal, descartando automáticamente los eventos stringSubject1.
  • Del mismo modo, le enviamos valores stringSubject2, nos conectamos stringSubject3y 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:






All Articles