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 objetosPassthroughSubject. - Nós enviamos
stringSubject1para o assunto principal. stringSubject1obtém o valor A.- Despachamos
stringSubject2para o assunto principal, descartando automaticamente os eventos stringSubject1. - Da mesma forma, enviamos valores para
stringSubject2, nos conectamosstringSubject3e 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: