Swift Combine¶
Customise handling of async. events by combining event-processing operators.
As explained by Brian Advent and Sebastian Boldt .
Publisher
Defines how values and error are produced
Value type
Allows registration of Scubsriber
Subscriber
Receives values and a completion
Reference type
Operator
Adopts publisher
Describes a behavior for changing values
Subscribes to a publisher (upstream)
Send to a subscriber (downstream)
Value type
protocol Subscriber {
associatedType Input
associatedType Failure: Error
}
protocol Publisher {
associatedType Output
associatedType Failure: Error
}
| RXSwift | Combine | |:————–:|:——————:| | Observable | Publisher | | Observer | Subscriber | | Disposable | Cancellable | | PublishSubject | PassthroughSubject |
Arrays, Strings or Dictionaries can be converted to Publishers in Combine.
let helloPublisher = "Hello Combine".publisher
let fibonacciPublisher = [0,1,1,2,3,5].publisher
let dictPublisher = [1:"Hello",2:"World"].publisher
You subscribe to publishers by calling sink(receiveValue: (value -> Void))
let fibonacciPublisher = [0,1,1,2,3,5].publisher()_ = fibonacciPublisher.sink { value in
print(value)
}
.finished
will be emitted if the subscription is finished.failure(_)
will be emitted if something went wrong
let fibonacciPublisher = [0,1,1,2,3,5].publisher
_ = fibonacciPublisher.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("finished")
case .failure(let never):
print(never)
}
}, receiveValue: { value in
print(value)
})
There are two types of subjects in combine.
PassthroughSubject
all events after subscription.CurrentValueSubject
most recent element after subscription.
let passThroughSubject = PassthroughSubject<String, Error>()
passThroughSubject.send("Hello")
passThroughSubject.sink(receiveValue: { value in
print(value)
})
passThroughSubject.send("World")
Will output World.
A simple example howto create a Publisher and a simple Subscriber can be found here and here.
let subject = CurrentValueSubject<String, Error>("Initial Value")
subject.send("Hello")
subject.send("World")
currentValueSubject.sink(receiveValue: { value in
print(value)
})
Will output World.
Special Subscribers
sink(receiveCompletion:receiveValue:)
assign(to:on:)
Operators¶
| RXSwift | Combine | | :————–: | :——————: | | map | map,tryMap | | subscribe | sink | | do | handleEvents | | flatMap | flatMap |
See playground.
Map¶
Transform elements.
[1,2,3,4].publisher.map {
return $0 * 10
}.sink { value in
print(value)
}
Scan¶
Aggregate elements.
[1,2,3,4].publisher.map {
return $0 * 10
}.sink { value in
print(value)
}
Filter¶
Filter elements.
[2,30,22,5,60,1].publisher.filter{
$0 > 10
}.sink { value in
print(value)
}
FlatMap¶
Transform elements.
// [Vadim Bulavin](https://www.vadimbulavin.com/map-flatmap-switchtolatest-in-combine-framework/)
print("flatMap")
struct User {
let name: CurrentValueSubject<String, Never>
}
let userSubject = PassthroughSubject<User, Never>()
userSubject
.flatMap { $0.name }
.sink { print($0) }
let user = User(name: .init("User 1"))
userSubject.send(user)
Propertywrapper¶
@EnvironmentObject
@ObserverableObject
@State
@Published
@Binding
Example uses combine to set isenabled
for a button, whenever canSendMessages
(e.g. via UISwitch
) is set to true
.
class ViewController: UIViewController {
...
@Published var canSendMessages: Bool = false
private var switchSubscriber:AnyCancellable?
override func viewDidLoad(){
super.viewDidLoad()
self.setupProcessingChain()
}
func setupProcessingChain(){
switchSubscriber = $canSendMessages.receive(on: DispatchQueue.main).assign(to: \isEnabled, on: sendButton)
}
@IBAction func didSwitch(_sender: UISwitch){
canSendMessages = sender.isOn
}
...
Use NotificationCenter to send a message and combine to link that message to a UITextField
.
extension Notification.Name {
static let newMessage = Notification.Name("newMessage")
}
struct Message {
let content: String
let author: String
}
...
class ViewController: UIViewController {
...
func setupProcessingChain(){
switchSubscriber = $canSendMessages.receive(on: DispatchQueue.main).assign(to: \isEnabled, on: sendButton)
let messagePublisher = NotificationCenter.Publisher(center: .default, name = .newMessage).map {notification -> String? in
return (notification.object as? Message).content ?? "" }
let messageSubscriber = Subscribers.Assign(object: messageLabel, keyPath: \.text)
messagePublisher.subscribe(messageSubscriber)
}
...
@IBAction func sendMessage(_ sender: Any){
let message = Message(content: "The current time is \(Date())", author: "Me")
NotificationCenter.default.post(name: .newMessage, object: message)
}