A declarative Swift API to process values over time

“Uma API declarativa do Swift para processar valores ao longo do tempo. Esses valores podem representar muitos tipos de eventos assíncronos”

Publishers

Os publishers são tipos que podem emitir valores ao longo do tempo para uma ou mais partes interessadas, como subscribers.

protocol Publisher {
    associatedtype Output // tipo de valores que o publisher emite
    associatedtype Failure: Error // tipo de erro que o publisher pode emitir

    func receive<S>(subscriber: S)
        where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

Um publisher pode emitir três tipos de eventos:

  • Output

  • Completion:

    • Finished
    • Failure

Subscribers

Um Subscriber se inscreve em um Publisher para receber seus valores.

protocol Subscriber {
    associatedtype Input // Tipo de valores que o subscriber recebe
    associatedtype Failure: Error // Tipo de erro que o subscriber pode receber

    // ...
}

Subscriptions

Subscriptions representam a conexão entre publishers e subscribers.

  • Publishers fornecem uma Subscription para seus subscribers:

  • Uma subscription possui o método .cancel() utilizado para desalocar uma assinatura.

Consumindo publishers

Combine nos fornece dois assinantes como operadores de um Publisher:

  • sink(receiveCompletion:receiveValue:)

let publisher = [0, 1, 2, 3, 4].publisher

let subscription = publisher
    .sink(
        receiveCompletion: { completion in
            print("Completion: \(completion)")
        },
        receiveValue: { value in
            print("Value: \(value)")
        }
    )

// Saída:
// Value: 0
// Value: 1
// Value: 2
// Value: 3
// Value: 4
// Completion: finished
  • assign(to:on:)

class MyClass {
    var anInt: Int = 0 {
        didSet {
            print("anInt was set to: \(anInt)", terminator: "; ")
        }
    }
}

var myObject = MyClass()
let myRange = (0...2)
let subscription = myRange.publisher.assign(to: \.anInt, on: myObject)

// Saída:
// "anInt was set to: 0; anInt was set to: 1; anInt was set to: 2"

Subjects

Subject é um publisher usado para “injetar” valores em um fluxo.

  • PassthroughSubject<Output, Failure>: permite que você publique novos valores sob demanda;
    • Útil para notificar interações do usuário

let subject = PassthroughSubject<String, Never>()

let subscription = subject
    .sink(
        receiveCompletion: { print("Completion: \($0)") },
        receiveValue: { print("Value: \($0)") }
    )

subject.send("Hello")
subject.send("World")
subject.send(completion: .finished)
subject.send("!")

// Saída:
// Value: Hello
// Value: World
// Completion: finished
  • CurrentValueSubject<Output, Failure>: baseado no PassthroughSubject, porém permite saber qual o seu valor atual:

let subject = CurrentValueSubject<Int, Never>(0)

let subscription = subject
    .sink(receiveValue: { print($0) })

subject.send(1)
subject.send(2)

print("current value \(subject.value)")

subject.value = 3

print("current value \(subject.value)")

// Saída:
// 0
// 1
// 2
// current value 2
// 3
// current value 3

Operators

Operators montam uma cadeia de republishers que processa elementos produzidos por publishers upstream.

let publisher = (1...3).publisher

let subscription = publisher
    .map { $0 * 2 }
    .sink(receiveValue: { print($0) })

// Saída:
// 2
// 4
// 6

Encadeamento de operadores

let publisher1 = [1, 1, 2, 2, 3, 3].publisher
let publisher2 = [4, 4, 5, 5, 6, 6].publisher

let formatter = NumberFormatter()
formatter.numberStyle = .spellOut

let subscription = publisher1
    .merge(with: publisher2)
    .removeDuplicates()
    .map { formatter.string(for: NSNumber(integerLiteral: $0)) ?? "" }
    .sink { print($0) }

// Saída:
// one
// two
// three
// four
// five
// six

Exemplo prático

Showcase – Combine e SwiftUI

Links úteis

Livros

Artigos

Vídeos

GitHub

View Github