The Combine framework provides an alternative to using callbacks or delegate methods when loading data from a web API. It’s actually based on a completely different paradigm: Reactive Programming.
By using Combine’s publishers, you can observe for value changes and react accordingly (e.g. reload a table view, update label values, etc).
Here’s a very simple use case. First, we’ll start with a view model, where we’ll make our network request:
class ContactListViewModel {
@Published var contacts: [Contact] = []
@Published var error: Error?
private let apiManager: ApiManager
func loadContacts() {
apiManager.fetchContacts { [weak self] result in
guard let self = self else { return }
switch result {
case .failure(let error):
self.error = error
case .success(let contacts):
self.contacts = contacts
}
}
}
init(apiManager: ApiManager) {
self.apiManager = apiManager
}
}
By adding the @Published
attribute to our contact array and error, we automatically create a publisher on these properties which can be observed for changes.
Next, in the view controller:
class ContactListViewController: UIViewController {
var viewModel: ContactListViewModel!
private var cancellables: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
setUpObervers()
viewModel.loadContacts()
}
func setUpObervers() {
viewModel.$contacts
.receive(on: RunLoop.main)
.sink { contacts in
// Update UI (e.g. reload a table view)
}.store(in: &cancellables)
viewModel.$error
.receive(on: RunLoop.main)
.sink { error in
// Show error alert
}.store(in: &cancellables)
}
}
By prefixing the properties for error
and contacts
with a dollar sign ($), we are actually accessing the publisher. This allows us to specify that we want to receive this on the main run loop (main thread). The sink
closure is where our updated data gets funneled to (think a sink with water flowing into it, the “water” being our data).
Finally, we need to store the result of this publisher, which is an AnyCancellable
object. By storing these objects, we’re connecting the lifecycle of the subscription to the owner (in this case, the view controller). This allows the system to properly retain and tear down the subscription in relation to the view controller’s life cycle. There’s a great article by Donny Wals on the purpose of AnyCancellable
objects.
There are many more parts of the Combine framework which can be put to use. This gives a basic, practical starting point which you can use right away in your projects.