At the end of the previous episode, I mentioned that I'm not quite happy yet with the implementation of the RootViewModel class. It passes an instance of the DarkSkyResponse struct to the RootViewController class via a completion handler. The RootViewController class still knows too much about the weather data and its origin.

We could rename the DarkSkyResponse struct to WeatherData, but I have a better solution in mind that takes advantage of protocol-oriented programming. Before I show you how we can improve the current implementation, I'd like to explain why and how protocols can help us decouple the DarkSkyResponse struct from the RootViewController class.

Why Protocols

To make sure the RootViewController class is unaware of the Dark Sky API, we won't be passing an instance of the DarkSkyResponse struct to the didFetchWeatherData closure of the RootViewModel class. Instead we define a protocol, WeatherData, that exposes an interface to the RootViewController class. The interface of the WeatherData protocol only provides the RootViewController class with the information it needs to populate its child view controllers.

Abstraction and Decoupling

If this doesn't make sense yet, then let me explain this with an analogy. The engine of a car is a complex piece of machinery and there are countless details the driver isn't interested in and shouldn't need to know about. In this analogy, the engine of the car is the DarkSkyResponse struct.

The driver doesn't need to know about the inner workings of the engine to drive the car. That's why the manufacturer of the car creates a dashboard, a steering wheel, a gearbox, etc. The manufacturer designed a protocol that abstracts the details of the engine from the driver. They could replace the engine with an electric one and the driver would still be able to drive the car.

The same is true for Rainstorm. The goal is to architect the application in such a way that it doesn't depend on the Dark Sky API. We should be able to use a different weather service with minimal effort, that is, without having to refactor the entire application.

Convenience and Flexibility

Protocols also give you the ability to introduce convenience and flexibility. The DarkSkyResponse struct isn't complex, but there are some improvements we could make. To access the weather forecast for the next few days, we need to access the data property of the daily property of the DarkSkyResponse struct. This isn't too bad, but I prefer a flatter object graph. Protocols can help us with that too.

Defining Protocols

The number of lines we need to write in this episode is surprisingly small. We start by creating a new group at the root of the project, Protocols. Create a new file by choosing the Swift File template from the iOS section and name the file WeatherData.swift.

Creating a New Swift File

Creating a New Swift File

We define a protocol WeatherData. The protocol defines the interface the RootViewController class will interact with. We need to carefully consider which pieces of data the RootViewController class should be able to access.

import Foundation

protocol WeatherData {

}

We define a property for the coordinates of the weather data, latitude of type Double and longitude of type Double. Notice that both properties are defined as gettable.

import Foundation

protocol WeatherData {

    var latitude: Double { get }
    var longitude: Double { get }

}

The RootViewController class should also be able to access the current weather conditions. To add some flexibility, we define a separate protocol for the current weather conditions, CurrentWeatherConditions. I prefer to keep closely related protocols within the same file. That's a personal choice.

import Foundation

protocol WeatherData {

    var latitude: Double { get }
    var longitude: Double { get }

}

protocol CurrentWeatherConditions {

}

The interface of the CurrentWeatherConditions protocol mirrors the interface of the DarkSkyResponse.Conditions struct. This means we can copy the property definitions and paste them into the CurrentWeatherConditions protocol. We replace the let keywords with var keywords and define the properties as gettable.

import Foundation

protocol WeatherData {

    var latitude: Double { get }
    var longitude: Double { get }

}

protocol CurrentWeatherConditions {

    var time: Date { get }
    var icon: String { get }
    var summary: String { get }
    var windSpeed: Double { get }
    var temperature: Double { get }

}

With the CurrentWeatherConditions protocol in place, we can continue with the definition of the WeatherData protocol. We define a property, current, of type CurrentWeatherConditions.

import Foundation

protocol WeatherData {

    var latitude: Double { get }
    var longitude: Double { get }

    var current: CurrentWeatherConditions { get }

}

protocol CurrentWeatherConditions {

    var time: Date { get }
    var icon: String { get }
    var summary: String { get }
    var windSpeed: Double { get }
    var temperature: Double { get }

}

We repeat these steps for the weather forecast. We define a protocol, ForecastWeatherConditions, which mirrors the interface of the DarkSkyResponse.Daily.Conditions struct.

import Foundation

protocol WeatherData {

    var latitude: Double { get }
    var longitude: Double { get }

    var current: CurrentWeatherConditions { get }

}

protocol CurrentWeatherConditions {

    var time: Date { get }
    var icon: String { get }
    var summary: String { get }
    var windSpeed: Double { get }
    var temperature: Double { get }

}

protocol ForecastWeatherConditions {

    var time: Date { get }
    var icon: String { get }
    var windSpeed: Double { get }
    var temperatureMin: Double { get }
    var temperatureMax: Double { get }

}

The CurrentWeatherConditions and ForecastWeatherConditions protocols are very similar but not identical. We could replace these protocols with a single protocol by defining some of the properties as optionals, but I try to avoid optionals as much as possible when I'm working with data structures.

There's a much better solution, protocol inheritance. We define another protocol, WeatherConditions. This protocol defines the property requirements CurrentWeatherConditions and ForecastWeatherConditions have in common.

import Foundation

protocol WeatherData {

    var latitude: Double { get }
    var longitude: Double { get }

    var current: CurrentWeatherConditions { get }

}

protocol WeatherConditions {

    var time: Date { get }
    var icon: String { get }
    var windSpeed: Double { get }

}

protocol CurrentWeatherConditions {

    var time: Date { get }
    var icon: String { get }
    var summary: String { get }
    var windSpeed: Double { get }
    var temperature: Double { get }

}

protocol ForecastWeatherConditions {

    var time: Date { get }
    var icon: String { get }
    var windSpeed: Double { get }
    var temperatureMin: Double { get }
    var temperatureMax: Double { get }

}

Protocol inheritance is an often overlooked feature of the Swift language. It allows us to simplify the current implementation ever so slightly. A protocol can inherit one or more protocols. This means it inherits the requirements of these protocols and it can add additional requirements of its own.

The changes we need to make are small. The CurrentWeatherConditions protocol should inherit the WeatherConditions protocol, which means we can remove the property requirements defined by the WeatherConditions protocol. The only property requirements defined by the CurrentWeatherConditions protocol are summary of type String and temperature of type Double.

import Foundation

protocol WeatherData {

    var latitude: Double { get }
    var longitude: Double { get }

    var current: CurrentWeatherConditions { get }

}

protocol WeatherConditions {

    var time: Date { get }
    var icon: String { get }
    var windSpeed: Double { get }

}

protocol CurrentWeatherConditions: WeatherConditions {

    var summary: String { get }
    var temperature: Double { get }

}

protocol ForecastWeatherConditions {

    var time: Date { get }
    var icon: String { get }
    var windSpeed: Double { get }
    var temperatureMin: Double { get }
    var temperatureMax: Double { get }

}

We repeat these steps for the ForecastWeatherConditions protocol. Because it inherits the WeatherConditions protocol, we can remove the property requirements defined by the WeatherConditions protocol. The ForecastWeatherConditions protocol defines two additional property requirements, temperatureMin of type Double and temperatureMax of type Double.

import Foundation

protocol WeatherData {

    var latitude: Double { get }
    var longitude: Double { get }

    var current: CurrentWeatherConditions { get }

}

protocol WeatherConditions {

    var time: Date { get }
    var icon: String { get }
    var windSpeed: Double { get }

}

protocol CurrentWeatherConditions: WeatherConditions {

    var summary: String { get }
    var temperature: Double { get }

}

protocol ForecastWeatherConditions: WeatherConditions {

    var temperatureMin: Double { get }
    var temperatureMax: Double { get }

}

The result looks quite nice if you ask me. The last piece of the puzzle is updating the WeatherData protocol. We define a property, forecast, of type [ForecastWeatherConditions].

protocol WeatherData {

    var latitude: Double { get }
    var longitude: Double { get }

    var current: CurrentWeatherConditions { get }
    var forecast: [ForecastWeatherConditions] { get }

}

The interface of the WeatherData protocol looks very similar to that of the DarkSkyResponse struct and that makes the work we need to do next that much easier.

Protocol Conformance

With the protocols defined, we can put them to use. Let me show you what I have in mind. Open RootViewModel.swift and navigate to the type alias we defined at the top. Instead of passing a DarkSkyResponse instance to the didFetchWeatherData closure, we pass a WeatherData instance to it.

import Foundation

class RootViewModel {

    // MARK: - Types

    enum WeatherDataError: Error {
        case noWeatherDataAvailable
    }

    // MARK: - Type Aliases

    typealias DidFetchWeatherDataCompletion = (WeatherData?, WeatherDataError?) -> Void

    ...

}

The compiler doesn't like this change, though. In the fetchWeatherData() method, we pass a DarkSkyResponse instance to the closure stored in the didFetchWeatherData property and the DarkSkyResponse struct currently doesn't conform to the WeatherData protocol. Let's take care of that now.

The compiler doesn't like this.

Open DarkSkyResponse.swift and define an extension for the DarkSkyResponse struct. We use the extension to conform the DarkSkyResponse struct to the WeatherData protocol.

extension DarkSkyResponse: WeatherData {

}

That's a good start, but it's obvious that the DarkSkyResponse struct doesn't meet the requirements defined by the WeatherData protocol. The compiler notices this too and it tries to help us by offering to add protocol stubs. Let's give that a try by clicking the Fix button.

The compiler offers help.

extension DarkSkyResponse: WeatherData {

    var current: CurrentWeatherConditions {
        <#code#>
    }

    var forecast: [ForecastWeatherConditions] {
        <#code#>
    }

}

That looks better. The implementation of the current property is straightforward. We return the value stored in the currently property of the DarkSkyResponse struct.

extension DarkSkyResponse: WeatherData {

    var current: CurrentWeatherConditions {
        return currently
    }

    var forecast: [ForecastWeatherConditions] {
        <#code#>
    }

}

The compiler warns us that DarkSkyResponse.Conditions doesn't conform to the CurrentWeatherConditions protocol. That's true. Fortunately, the solution is simple.

Create an extension for the DarkSkyResponse.Conditions struct and conform the DarkSkyResponse.Conditionsstruct to theCurrentWeatherConditions` protocol. The compiler error disappears as a result. You may be wondering how that's possible?

extension DarkSkyResponse: WeatherData {

    var current: CurrentWeatherConditions {
        return currently
    }

    var forecast: [ForecastWeatherConditions] {
        <#code#>
    }

}

extension DarkSkyResponse.Conditions: CurrentWeatherConditions {

}

The interface of the DarkSkyResponse.Conditions struct and the CurrentWeatherConditions protocol are very similar. The DarkSkyResponse.Conditions struct automatically meets the requirements of the CurrentWeatherConditions protocol. There's nothing we need to add to make the DarkSkyResponse.Conditions struct conform to the CurrentWeatherConditions protocol.

Revisit the extension for the DarkSkyResponse struct. The implementation of the forecast property isn't difficult either. We return the value stored in the data property of the daily property.

extension DarkSkyResponse: WeatherData {

    var current: CurrentWeatherConditions {
        return currently
    }

    var forecast: [ForecastWeatherConditions] {
        return daily.data
    }

}

extension DarkSkyResponse.Conditions: CurrentWeatherConditions {

}

The compiler throws another error, similar to the one it threw earlier. The DarkSkyResponse.Daily.Conditions struct doesn't conform to the ForecastWeatherConditions struct. You probably know what we need to do to resolve the problem.

Create an extension for the DarkSkyResponse.Daily.Conditions struct and conform the DarkSkyResponse.Daily.Conditions struct to the ForecastWeatherConditions protocol. The compiler error disappears automatically.

extension DarkSkyResponse: WeatherData {

    var current: CurrentWeatherConditions {
        return currently
    }

    var forecast: [ForecastWeatherConditions] {
        return daily.data
    }

}

extension DarkSkyResponse.Conditions: CurrentWeatherConditions {

}

extension DarkSkyResponse.Daily.Conditions: ForecastWeatherConditions {

}

That's it. Revisit RootViewModel.swift. The compiler error we encountered earlier should disappear because the DarkSkyResponse struct now conforms to the WeatherData protocol. If you build the application, no errors or warnings should pop up.

What Did We Gain?

You may still be wondering why it's necessary to define these protocols. It seems we only complicated the project by adding a layer of abstraction. Let me summarize what we gained.

First, the RootViewController class no longer knows about the DarkSkyResponse struct. Instead the didFetchWeatherData closure of the RootViewModel class passes it a WeatherData instance. In other words, we decoupled the RootViewController class from the DarkSkyResponse struct. Protocols are a powerful tool for decoupling code. It makes your code more flexible, more reusable, and more testable.

It's important to understand that the object the RootViewModel class passes to the RootViewController instance is of type DarkSkyResponse. The RootViewController class doesn't know that, though. The RootViewController class doesn't care as long as it conforms to the WeatherData protocol. It's easy to prove that the RootViewController instance receives an object of type DarkSkyResponse.

Open RootViewController.swift, navigate to the setupViewModel(with:) method, and cast the data constant that is passed as an argument to the closure to a DarkSkyResponse instance. If that works, we should be able to access the properties of the DarkSkyResponse struct that aren't exposed by the WeatherData protocol, for example, the daily property.

private func setupViewModel(with viewModel: RootViewModel) {
    // Configure View Model
    viewModel.didFetchWeatherData = { [weak self] (data, error) in
        if let _ = error {
            // Notify User
            self?.presentAlert(of: .noWeatherDataAvailable)
        } else if let data = data as? DarkSkyResponse {
            print(data.daily)
        } else {
            // Notify User
            self?.presentAlert(of: .noWeatherDataAvailable)
        }
    }
}

Run the application to see if that works. It works.

Daily(data: [Rainstorm.DarkSkyResponse.Daily.Conditions(time: 2049-07-12 07:00:00 +0000, icon: "partly-cloudy-day", windSpeed: 2.52, temperatureMin: 59.210000000000001, temperatureMax: 88.159999999999997), Rainstorm.DarkSkyResponse.Daily.Conditions(time: 2049-07-13 07:00:00 +0000, icon: "partly-cloudy-night", windSpeed: 3.4100000000000001, temperatureMin: 59.369999999999997, temperatureMax: 86.989999999999995), Rainstorm.DarkSkyResponse.Daily.Conditions(time: 2049-07-14 07:00:00 +0000, icon: "partly-cloudy-day", windSpeed: 4.25, temperatureMin: 56.060000000000002, temperatureMax: 85.359999999999999), Rainstorm.DarkSkyResponse.Daily.Conditions(time: 2049-07-15 07:00:00 +0000, icon: "partly-cloudy-day", windSpeed: 3.2599999999999998, temperatureMin: 58.5, temperatureMax: 86.579999999999998), Rainstorm.DarkSkyResponse.Daily.Conditions(time: 2049-07-16 07:00:00 +0000, icon: "partly-cloudy-day", windSpeed: 3.3199999999999998, temperatureMin: 61.030000000000001, temperatureMax: 93.219999999999999), Rainstorm.DarkSkyResponse.Daily.Conditions(time: 2049-07-17 07:00:00 +0000, icon: "partly-cloudy-day", windSpeed: 3.3799999999999999, temperatureMin: 60.560000000000002, temperatureMax: 91.629999999999995), Rainstorm.DarkSkyResponse.Daily.Conditions(time: 2049-07-18 07:00:00 +0000, icon: "partly-cloudy-night", windSpeed: 3.25, temperatureMin: 61.329999999999998, temperatureMax: 93.510000000000005), Rainstorm.DarkSkyResponse.Daily.Conditions(time: 2049-07-19 07:00:00 +0000, icon: "partly-cloudy-night", windSpeed: 3.0800000000000001, temperatureMin: 62.469999999999999, temperatureMax: 94.219999999999999)])

Remember the car analogy from earlier in this episode. When you buy a car, nothing stops you from looking under the hood and inspecting the internals of the engine. The concrete type of the data constant is DarkSkyResponse. That's the power of protocol-oriented programming. The RootViewController class is interested in an interface it can use to populate its child views. It isn't interested in the concrete type of the data constant.

Second, the protocols we defined make working with the weather data easier. The object graph is flatter and we don't need to traverse it to access the weather forecast for the next few days. This is a minor improvement but nonetheless an improvement.

Third, we added flexibility to the project. Rainstorm currently uses the Dark Sky API for fetching weather data, but it's now easier to switch to a different weather service. The only object that knows about the Dark Sky API is the RootViewModel class. We could even add another weather service as a fallback, for example, if the Dark Sky API is down or if the API key is compromised. The rest of the application wouldn't need to know about this change.

If your car has four-wheel drive and the transmission no longer powers the rear axle, you won't notice much as a driver. You may notice a decrease in performance, but everything looks and feels the same from the driver's viewpoint. The same is true for Rainstorm.

The Bigger Picture

Protocol-oriented programming isn't unique to Swift, but it certainly gained momentum when Swift was introduced. The Swift team explicitly describes the language as a protocol-oriented programming language that embraces protocols and value types.

Object-oriented programming still has its place and reference types are indispensable if you're building Cocoa applications. But knowing and understanding the nature of the language is important if you want to take full advantage of its possibilities.

In this episode we explored the benefits of protocol-oriented programming, but I'd like to highlight another important concept of software development. We introduced protocols in this episode to decouple the RootViewController class from the Dark Sky API and the DarkSkyResponse struct. The protocols we defined act as a layer of abstraction to keep the RootViewController class ignorant. Keeping objects ignorant is a powerful recipe for a flexible and modular architecture. We revisit this concept several more times as we build Rainstorm.