misiel.

engineer ∙ gamer ∙ creator

Published on

Networking with Combine

Authors

Throughout the past couple of weeks or so, I've been working on a "simple" Dictionary app that displays data, makes API calls, handles navigation and overall runs me through some core concepts of iOS app development.

One of the parts that I enjoyed the most so far has been adding networking to the app. Now there are a plethora of combinations of networking libraries and data binding frameworks to choose from in iOS, but I opted for a simple combo that reminded me of some classic Retrofit/RxKotlin from Android - I went with URLSession and Combine... I will not be elaborating any further on this decision 😂.

I'm just kidding - really it just came down to URLSession being a native apple framework that was easy to get the hang of after a tutorial video or two.

On the other hand, Combine was the more interesting choice. At the beginning when I decided I wanted to build this app in Model-View-ViewModel (MVVM) architecture, the sore thumb that stuck out to me was "how the heck am I going to get my Views to change their values reactively from an API call?"

Well past and more inexperienced Misiel, using a reactive framework of course! So why not RxSwift? Well after a little bit of research, it looks like the industry standard is heading towards Combine, but honestly once you learn one reactive framework, the concepts and structure are all pretty much the same and your choice between them just comes down to granular pros/cons imo.

Observing ViewModel data in our View

Combine makes subscribing to data pretty easy. Take a look at the following code snippet of HomeViewController.swift and HomeViewModel.swift:

import Combine
class HomeViewModel {
    @Published var fetchedRandomWord = default_home_word

    func fetchRandomWord () {
        some API call through URLSession...
        fetchedRandomWord = API call's returned value
    }
}
---------------------------------------------------------------
import Combine
class HomeViewController: UIViewController {
    let homeVM = HomeViewModel() // in reality I actually use HomeViewModel.shared as a "singleton" instance here, but simplifying things for the sake of this post!
    private let cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()

        homeVM.$fetchedRandomWord
            .sink { [weak self] randomWord in
                self?.refreshUI(with: randomWord)
            }
            .store(in: &cancellables)
    }
    ...
}

Let's break down a few things here:

  1. @Published var fetchedRandomWord = default_home_word

The @Published property wrapper is a feature of Combine that lets us know that this property is able to be subscribed to. This is what let's us call homeVM.$fetchedRandomWord in our HomeViewController.

  1. homeVM.$fetchedRandomWord.sink { }

To actually subscribe to the property and its changes in value, we must call .sink{} on the property (Really Combine, you couldn't just make it .subscribe or .observe? 🙄). In the .sink closure, the value of fetchedRandomWord is able to be used as so:

  1. [weak self] randomWord in self?.refreshUI(with: randomWord)

In this code bit, you see the fetchedRandomWord value is renamed to randomWord and then being passed along into a refreshUI() function.

A slightly important callout here is the usage of the weak self reference. Without getting too much into it as I would love to write another post diving deeper on this, we use a weak reference to self so that we avoid a retention cycle. Since our ViewController has a reference to our ViewModel, we cannot have a closure of our ViewModel also have a strong reference to our ViewController or else both classes will not be able to be discarded in memory by Swift's ARC as their references to each other will be 1 and 1 and never hit 0.

  1. .store(in: &cancellables)

And lastly, because .sink returns a subscription (aka Cancellable), we can store it into a set of Cancellables which is a type provided by Combine that allows us to no longer receive events from the publisher. Usually, by storing your cancellables in a "Cancellable Container" (private let cancellables = Set<AnyCancellable>()), whenever the container is deallocated your cancellables will also be cleared as well which is a pretty nifty thing when it comes to handling memory leaks!

Conclusion

And that's pretty much it! There's still much more to learn in Combine for me, but so far it has been a handy reactive programming framework to pick up and fairly simple as well.

To recap here's a couple of things we learned:

  1. @Published property wrapper - if you see this, you can subscribe to it.
  2. $publishedProperty.sink{} - you're now subscribing. Use the value of the property as you see fit.
  3. Cancellable - remember this is a subscription and you don't want to get charged accidentally! Be sure to clear your subscriptions by using a Cancellable container or calling cancellable.cancel().

Thanks for reading!