There are a few user interface issues we need to address in this episode. The day view controller no longer shows its activity indicator view while it is waiting for weather data and the user interface elements that show the weather data should only be shown when there is weather data to display. Let's find out how we can resolve these issues.

Bridging the Gap Between Imperative and Reactive Programming

We have a few options to implement a solution. The Published property wrapper is the most obvious solution. We can declare a variable property, loading, of type Bool and apply the Published property wrapper to it. The publisher of the loading property communicates to its subscribers when the root view model is fetching weather data.

This solution works, but it means more state we need to manage. The solution I have in mind makes use of a subject. Open RootViewModel.swift and declare a private, constant property, loadingSubject, of type PassthroughSubject<Bool, Never>. The Output type is Bool and the Failure type is Never. The initializer accepts no arguments.

private let loadingSubject = PassthroughSubject<Bool, Never>()

What is a subject and how can the PassthroughSubject class help us? A subject bridges the gap between imperative programming and reactive programming. Let's find out what that means and when it is appropriate to use a subject.

Subject is a protocol and PassthroughSubject is a class conforming to that protocol. The Subject protocol inherits from the Publisher protocol, which means that a subject is a publisher, emitting values like any other publisher. The key difference is that a subject can inject values into the stream of values it emits. Let me show you how this works.

The Subject protocol defines the send(_:) method to inject a value into the stream of values the subject emits. The value that is passed to the send(_:) method is the value that is injected. This is useful to bridge the gap between the imperative code and the reactive code in a project. We explore how this works in a moment.

PassthroughSubject and CurrentValueSubject

The Combine framework defines two classes that conform to the Subject protocol, PassthroughSubject and CurrentValueSubject. Apple did a great job naming these classes, avoiding any confusion. As the name suggests, the values a PassthroughSubject instance emits are transient or short-lived. In other words, the subject doesn't hold on to the values it publishes. It doesn't hold on to state. This is important to know and understand. The values pass through the subject hence the name of the class.

The CurrentValueSubject class works a bit differently. As the name suggests, you can ask a CurrentValueSubject instance for its current value through its value property. A CurrentValueSubject instance holds on to the last value it emitted. That is its current value. The behavior is similar to a property with the Published property wrapper applied to it. You can emit a value with a CurrentValueSubject instance by passing a value to its send(_:) method or by updating its value property.

Because a CurrentValueSubject instance holds on to the last value it emitted, the initializer accepts an initial value. Because a passthrough subject doesn't hold on to the last value it emitted, the initializer of the PassthroughSubject class accepts no initial value.

An application should manage as little state as possible and for that reason I recommend defaulting to PassthroughSubject unless you have a valid need to access the last emitted value of the subject.

Sending Values with Subjects

Navigate to the fetchWeatherData(for:) method in RootViewModel.swift. The root view model can communicate to other objects when it is fetching weather data by sending values through loadingSubject. When it resumes the weather data task in its fetchWeatherData(for:) method, it calls send(_:) on loadingSubject, passing in true. The send(_:) method injects a value into the stream of values loadingSubject publishes.

// MARK: - Helper Methods

private func fetchWeatherData(for location: CLLocation) {
    // Cancel In Progress Data Task
    weatherDataTask?.cancel()

    // Helpers
    let latitude = location.coordinate.latitude
    let longitude = location.coordinate.longitude

    // Create URL
    let url = WeatherServiceRequest(latitude: latitude, longitude: longitude).url

    // Create Data Task
    weatherDataTask = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
        DispatchQueue.main.async {
            self?.didFetchWeatherData(data: data, response: response, error: error)
        }
    }

    // Start Data Task
    weatherDataTask?.resume()
    loadingSubject.send(true)
}

In the completion handler of the data task, the root view model calls send(_:) on loadingSubject, passing in false, to indicate that it finished fetching weather data, successfully or unsuccessfully.

// MARK: - Helper Methods

private func fetchWeatherData(for location: CLLocation) {
    // Cancel In Progress Data Task
    weatherDataTask?.cancel()

    // Helpers
    let latitude = location.coordinate.latitude
    let longitude = location.coordinate.longitude

    // Create URL
    let url = WeatherServiceRequest(latitude: latitude, longitude: longitude).url

    // Create Data Task
    weatherDataTask = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
        DispatchQueue.main.async {
            self?.loadingSubject.send(false)
            self?.didFetchWeatherData(data: data, response: response, error: error)
        }
    }

    // Start Data Task
    weatherDataTask?.resume()
    loadingSubject.send(true)
}

There is a good reason why the loadingSubject property is a private, constant property. First, there is no need to make it a variable property. Second, other objects should not have access to the loadingSubject property. Only the root view model should have the ability to inject values into the stream of values of loadingSubject.

We use a computed property to expose the publisher of loadingSubject. This is straightforward and a common pattern. Declare a computed property, loadingPublisher, of type AnyPublisher<Bool, Never>. The Output and Failure type of loadingPublisher match those of loadingSubject. In the body of the computed property, the root view model returns loadingSubject. It calls eraseToAnyPublisher to hide or erase the type of loadingSubject. We covered this pattern several times in this series.

var loadingPublisher: AnyPublisher<Bool, Never> {
    loadingSubject
        .eraseToAnyPublisher()
}

private let loadingSubject = PassthroughSubject<Bool, Never>()

Updating the Day View Model

We need to pass loadingPublisher to the day view model. Open DayViewModel.swift and declare a constant property, loadingPublisher, of type AnyPublisher<Bool, Never>. The Output and Failure type of loadingPublisher match those of the loadingPublisher computed property of the RootViewModel class.

import UIKit
import Combine

struct DayViewModel {

    // MARK: - Properties

    let loadingPublisher: AnyPublisher<Bool, Never>
    let weatherDataPublisher: AnyPublisher<WeatherData, Never>
    let weatherDataErrorPublisher: AnyPublisher<WeatherDataError, Never>

    ...

}

Open RootViewController.swift, navigate to the prepare(for:sender:) method, and update the memberwise initializer of the DayViewModel struct.

self.dayViewController.viewModel = DayViewModel(loadingPublisher: viewModel.loadingPublisher,
                                                weatherDataPublisher: viewModel.weatherDataPublisher,
                                                weatherDataErrorPublisher: viewModel.weatherDataErrorPublisher)

Updating the Day View Controller

Let's put the subject to use in the DayViewController class. In setupBindings(), the day view controller attaches a subscriber to its view model's loadingPublisher property by invoking the assign(to:on:) method. We covered this pattern in the previous episode. The root view model passes isHidden as the key path and weatherDataContainerView as the object. The AnyCancellable object returned by the assign(to:on:) method is added to subscriptions.

viewModel?.loadingPublisher
    .assign(to: \.isHidden, on: weatherDataContainerView)
    .store(in: &subscriptions)

When a data task is in progress, loadingPublisher emits true and the isHidden property of weatherDataContainerView is set to true. This means that the weather data container view is hidden as long as the root view model is fetching weather data.

The activity indicator view shouldn't be hidden when the root view model is fetching weather data. The day view controller applies the map operator to its view model's loadingSubject property. The closure that is passed to the map operator is as simple as it gets. The day view controller uses the logical NOT operator to invert the boolean values the publisher emits. true becomes false and false becomes true. It invokes the assign(to:on:) method, passing in isHidden as the key path and activityIndicatorView as the object.

viewModel?.loadingPublisher
    .map { !$0 }
    .assign(to: \.isHidden, on: activityIndicatorView)
    .store(in: &subscriptions)

Before we build and run the application, we need to clean up the day view model's updateView() method. We no longer need to invoke stopAnimating() on activityIndicatorView and there is no need to manually set the isHidden property of weatherDataContainerView.

// MARK: - View Methods

private func updateView() {
    if let viewModel = viewModel {

    } else {
        messageLabel.isHidden = false
        messageLabel.text = "Cloudy was unable to fetch weather data."
    }
}

Build and run the application to see the result. The user interface issues of the day view controller should be resolved.

What's Next?

The Published property wrapper is a welcome addition, but it shouldn't be your default choice for creating publishers. Reactive programming has many benefits and one of them is reducing the amount of state an application manages. The RootViewModel class uses a passthrough subject because there is no need to hold on to the loading state. The root view model communicates the loading state to other objects, but it doesn't hold on to it. The root view model forgets about the loading state as soon as its subject publishes it.