6 Operadores de colheitadeira Swift que você deve conhecer

A tradução do artigo foi preparada em antecipação ao início do curso avançado "Desenvolvedor iOS".








Neste artigo, veremos seis operadores Combine úteis. Faremos isso com exemplos, experimentando cada um no Xcode Playground.



O código-fonte está disponível no final do artigo.



Bem, sem mais delongas, vamos começar.



1. independente



Este grupo de declarações nos permite anexar (literalmente “anexar”) eventos, valores ou outros editores ao nosso 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: Helloe World! são produzidos em ordem sequencial:







Agora vamos adicionar outro editor do mesmo 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)
}


O resultado é semelhante ao anterior (note que precisamos enviar um evento .finishedpara assunto para que o operador .prependfuncione):







2. anexar



O operador .append(literalmente "adicionar ao final") funciona de forma semelhante .prepend, mas, neste caso, adicionamos valores ao editor original:



func appendOutputExample() {
    let stringPublisher = ["Hello"].publisher
    
    stringPublisher
        .append("World!")
        .sink(receiveValue: { print($0) })
        .store(in: &subscriptions)
}


Como resultado, vemos Helloe World! enviado para o console:







Semelhante ao que usamos anteriormente .prependpara adicionar outro Publishera, também temos esta opção para o operador .append:







3.switchToLatest



Um operador mais complexo .switchToLatestnos permite combinar uma série de editores em um único fluxo 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)
}


Aqui está o que está acontecendo no código:



  • Criamos três objetos PassthroughSubjectpara os quais enviaremos valores.
  • Criamos um objeto principal PassthroughSubjectque despacha outros objetos PassthroughSubject.
  • Nós enviamos stringSubject1para o assunto principal.
  • stringSubject1 obtém o valor A.
  • Despachamos stringSubject2para o assunto principal, descartando automaticamente os eventos stringSubject1.
  • Da mesma forma, enviamos valores para stringSubject2, nos conectamos stringSubject3e despachamos um evento de conclusão para ele.


O resultado é a saída A, C, De G:







Para simplificar, a função isAvailableretorna um valor aleatório Booldepois de algum atraso.



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")
    }
}


Graças ao operador, .switchToLatestalcançamos o que desejamos. Apenas um valor Bool será exibido:







4. mesclar (com :)



Costumamos .merge(with:)combinar dois Publisherss como se estivéssemos obtendo valores de apenas um:



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")
}


O resultado é uma sequência alternada de elementos:







5.combineLatest



O operador .combineLatestpublica uma tupla contendo o valor mais recente de cada editor.



Para ilustrar isso, considere o seguinte exemplo do mundo real: temos um nome de usuário, senha UITextFieldse um botão de continuar. Queremos manter o botão desabilitado até que o nome de usuário tenha pelo menos cinco caracteres e a senha pelo menos oito caracteres. Podemos fazer isso facilmente usando o 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")
}


Uma vez usernameTextField e passwordTextFieldreceba user12, e 12345678consequentemente, a condição é satisfeita e o botão é ativado:







6. zip



O operador .zipfornece um par de valores correspondentes de cada editor. Digamos que desejamos determinar se ambos os editores publicaram o mesmo 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) //  ,       
}


Temos os seguintes valores correspondentes de intSubject1e intSubject2:



  • 0 e 4
  • 1 e 1
  • 6 e 7


O último valor 9não é exibido porque o intSubject1valor correspondente ainda não foi publicado:







Recursos



O código-fonte está disponível no Gist .



Conclusão



Interessado em outros tipos de operadores Combine? Sinta-se à vontade para visitar meus outros artigos:






All Articles