Learn & Master ⚔️ the Basics of Combine in 5 Minutes

Sebastian Boldt
Jul 9, 2019 · 5 min read

Because RxSwift and Combine have so much in common I thought it would be a great idea to adapt the things I know about RxSwift and use them to create something new.

This is an article based on one of my most successful articles here on Medium (Learn and Master Rx-Swift). More than 140.000 Impressions so far. That’s Amazing, thank you so much.

So lets get started with Apples take on reactive programming: COMBINE

1. Publishers

Arrays, Strings or Dictionaries can be converted to Publishers in Combine.
Let’s create some simple ones.

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))
The passed block will receive all values emitted by that publisher.

let fibonacciPublisher = [0,1,1,2,3,5].publisher()_ = fibonacciPublisher.sink { value in
print(value)
}
OUTPUT:
0 1 1 2 3 5

Publishers can emit zero or more values over their lifetimes.
Besides the basic values your Publisher also emits special values represented by the Subscribers.Completion enum.

  • .finished will be emitted if the subscription is finished
  • .failure(_) will be emitted if something went wrong

The associated value for the failure case can be a custom Object, an Error or a special Never object that indicates that the Publisher won’t fail.

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)
})
OUTPUT:
0 1 1 2 3 5
finished

If you want to cancel a subscription you can do that by calling cancel on it.

let subscriber = fibonacciPublisher.sink { value in
print(value)
}
subscriber.cancel()

2. Subjects 📫

  • PassthroughSubject: If you subscribe to it you will get all the events that will happen after you subscribed.
  • CurrentValueSubject: will give any subscriber the most recent element and everything that is emitted by that sequence after the subscription happened.

PassthroughSubject

let passthroughObject = PassthroughSubject<String,Error>()

You can add new Values to that subject by using the send(input:String) function. If you want to complete the sequence you need to use send(completion: .finished).

send(completion: someError) will emit an error.
Let’s add some values to our PublishSubject.

passthroughObject.send("Hello")
passthroughObject.send("World")

If you subscribe to that subject after adding “Hello” and “World” using send(), you won’t receive these two values. You will just receive values that where sent after you subscribed to the Subject

let passThroughSubject = PassthroughSubject<String, Error>()
passThroughSubject.send("Hello")
passThroughSubject.send("World")
passThroughSubject.sink(receiveValue: { value in
print(value)
})
OUTPUT:
NO OUTPUT

Now we send a value after we subscribed and vola. The value will be printed.

let passThroughSubject = PassthroughSubject<String, Error>()
passThroughSubject.send("Hello")
passThroughSubject.sink(receiveValue: { value in
print(value)
})
passThroughSubject.send("World")OUTPUT:
World

CurrentValueSubject

let subject = CurrentValueSubject<String, Error>("Initial Value")
subject.send("Hello")
subject.send("World")
currentValueSubject.sink(receiveValue: { value in
print(value)
})
OUTPUT:
World

Congratulations 🎉. If you kept up reading to this point you should know the basics of Combine. There is a lot more to learn, but everything around Combine is based on these simple principles. You can take a short break now and play around with these concepts to fully understand them. If you are ready let us continue because there is a lot more interesting stuff to uncover.

3. Mapping, Transforming, Filtering ⚙️

3.1 Map

Imagine a transformation that multiplies each value of a sequence with 10 before emitting

[1,2,3,4].publisher().map {
return $0 * 10
}.sink { value in
print(value)
}
OUTPUT: 10 20 30 40

3.2 Scan

[1,2,3,4,5].publisher().scan(0) { seed, value in
return
seed + value
}.sink { value in
print(value)
}
OUTPUT: 1 3 6 10 15

3.3 Filter

[2,30,22,5,60,1].publisher().filter{
$0 > 10
}.sink { value in
print(value)
}
OUTPUT: 30 22 60

Other filter operators you should try:

  • flatMap
  • replaceEmpty
  • max & min

4. Combine & SwiftUI/UIKit

UIKit-Example:
A ViewModel-Property directly attached to its Control

import Combine
import UIKit
struct ViewModel {
@Published var switchState: Bool = false
}
final class SomeViewController: UIViewController {
private var subscriber: AnyCancellable?
private var viewModel = ViewModel()

@IBOutlet private weak var aSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
let queue = DispatchQueue.main
subscriber = publisher.assign(to: \.isEnabled,
on: aSwitch)
}

@IBAction func didSwitch(_ sender: UISwitch) {
viewModel.switchState = sender.isOn
}
}

It’s a wrap 🎁

If the article has helped you and you want me to continue writing similar articles, you are welcome to support me with a small donation

🤜🏾 🤛

Feel free to add me on github, twitter, linkedin or xing if you have any questions. If you like electronic music you can also listen to my Tracks on SoundCloud ;)

iOS App Development

Stories and technical tips about building apps for iOS…

iOS App Development

Stories and technical tips about building apps for iOS, Apple Watch, and iPad/iPhone

Sebastian Boldt

Written by

Creative Head, iOS Developer @ Immowelt, DJ/Producer from Hamburg. Creator of HabitBe, a weekly Habit Tracker written in SwiftUI. https://www.sebastianboldt.com

iOS App Development

Stories and technical tips about building apps for iOS, Apple Watch, and iPad/iPhone