Chicken Scratches

Dependency Mixins in Swift using Protocol-Oriented-Programming

Here I discuss a pattern of dependency management using protocols and extensions to create Dependency Mixins.

Inter-component dependencies

One of the fundamental principles of good software engineering is separation of concerns: keeping components small and self-contained, while carefully managing inter-component dependencies. Dependencies are necessary, of course, but the principle of loose-coupling means designing so that each component can be changed or replaced without large disruptions to the rest of the system.

Even more importantly, it means we can test each component individually, giving us greater confidence in the correctness of our software.

The first step in achieving loose-coupling is to separate each component’s implementation from its interface. In Swift, this means creating a protocol which defines the class’s public interface. Then, classes which depend on it can refer to the protocol and don’t need to care about the behind the scenes implementation.

Once you have that separation, the question becomes how and where to instantiate your concrete components, and how each component accesses its dependencies. Many developers, including Apple themselves, resort to using singletons or global variables. This is considered an “anti-pattern” because it violates the loose coupling principle and ties your components more tightly to their dependencies’ implementation.

Since you can’t instantiate a protocol directly or access it as a singleton, the protocol-based approach forces you to think about better ways to get access. Two common patterns for this are Dependency Injection and Service Locator. They each have their pluses and minuses. One of the biggest challenges, especially for inexperienced teams, is that it takes more time and effort to use these techniques rather than simply using globals or singletons.

As we know, when something takes more time and effort, it often doesn’t get done.

I present here a technique which facilitates either approach, based on the concept of Mixins using Swift’s Protocol-Oriented Programming. We can call this pattern the Dependency Mixin.

Injection and Locators

Dependency Injection has been covered extensively in the literature, so I won’t go into too much detail here, but the essential concept is that a class, instead of instantiating its dependencies directly, is given them. This is often done by passing them as parameters to init (Constructor Injection) or by setting properties immediately after initialization (Setter Injection). This allows you to decide at the time of instantiation which implementation of each dependency to pass. For example, in your unit test you can pass a mock implementation of a service class. Or if you decide to reimplement that class at some point, you only need to change the implementation behind the scenes while keeping the protocol the same, so none of the dependent classes need to be updated.

In the Service Locator pattern, your main class has an explicit dependency on a global Service Locator object, which is in charge of finding all other dependencies by name or tag. Often services will self-register with the Locator.

When it is practical to use Constructor Injection, that is my preferred approach, but there are times when it is cumbersome. For example, if you have a service that is required by almost all components in the system, like a logging service, you end up passing that service to every constructor and following chains of dependency down long hierarchies. Other times, the number of dependencies can grow large, making the initializer unwieldy. And finally, in iOS development you sometimes don’t get to directly instantiate your classes, such as when using Storyboards to create View Controllers, or when fetching Core Data objects.

Mixins for dependencies

Protocol-Oriented Mixins provide an alternative — a bit less “pure” in their approach but perhaps more pragmatic for real-world programming situations. I don’t recommend dogmatically picking one paradigm and enforcing it everywhere, but rather choosing what works best in each situation. This Mixin pattern is helpful in the above-mentioned scenarios. Also note that it can be used to easily implement either the Service Locator pattern or Setter Injection, as I will describe below.

Mixins are a common concept in modern object-oriented programming languages. You can think of them as a limited form of multiple-inheritance which allow adding methods but not data. (In Swift terminology, functions and computed properties but not stored properties.) This is accomplished in Swift by defining a protocol and then an extension on that protocol. Like so:

protocol MyMixin {
    var myComputedProperty:String { get }
    func myFunction() -> Int
}

extension MyMixin {
    var myComputedProperty:String {
        return "Hello world"
    }

    func myFunction() -> Int {
        return 42
    }
}

Then you can simply declare that your class implements the MyMixin protocol and you get myComputedProperty and myFunction for free.

The methods in the example above return basic types to keep it simple, but you can do whatever you want in your mixin methods, meaning you can use them to get access to dependencies, without the implementing class having to know how it’s done.

Now let’s look at some code. As is typical, this code is simplified drastically from a real-world scenario in order to focus on the salient points.

Example

DataManager.swift

This file defines the interface for our Data Manager. We define it as a protocol so that we can more easily create a mock version of it for unit testing. This also forces us to think about the public interface of the class and encourages a clean separation of concerns.

protocol DataManager {
    func save(item:Any)
    func getItem(id:String) -> Any
}

/// This is a protocol that should be implemented by classes who want access to the system-wide Data
/// Manager object. In conjunction with the extension defined later on, this acts as a Mixin.
protocol RequiresDataManager {
    var dataManager:DataManager { get }
}

DataManagerImpl.swift

The implementation of our Data Manager. A test build may omit this file and include instead a DataManagerMock class which skips network calls or whatever. This allows us to more easily test classes which rely on the Data Manager.

The test build doesn’t necessarily have to omit this file, as it can inject the mock version on a test-by-test basis.

class DataManagerImpl : DataManager {
    func save(item: Any) {
        // Do something here
    }
    func getItem(id: String) -> Any {
        // Do something here
        return "something"
    }
}


Log.swift

Here’s the interface for a system-wide logging class. The separation of concerns is handled in the same way as the Data Manager above.

protocol Log {
    func debug(_ txt:String)
    func error(_ txt:String)
}
protocol RequiresLog {
    var log:Log { get }
}

LogImpl.swift

And a simple implementation of Log.

class LogImpl : Log {
    func debug(_ txt: String) {
        print("DEBUG: \(txt)")
    }
    func error(_ txt: String) {
        print("ERROR: \(txt)")
    }
}

Somewhere in your main application

A good place to initialize system-wide services is in AppDelegate.swift, or if that becomes unwieldy, in a separate initializer class.

Here we define an extension to the RequiresDataManager protocol to give access to the system’s DataManager implementation.

This code is not included in your unit tests. The code in your test harness would be similar, except that it would instantiate an instance of DataManagerMock (not shown here), which could be a class that implements the DataManager protocol with stubs or mock functions.

You could also include conditional logic in the dataManager computed property that returns a mock version only when desired.

Remember, you can do anything you want in the implementations of dataManager and log. This example simply returns fileprivate variables, but you can easily call out to a Service Locator, for example. It’s a bit tricky, but you can also implement the Setter Injection pattern. If there is interest, I can describe how in a future post — spoiler: it involves using objc_setAssociatedObject.

fileprivate let theDataManager = DataManagerImpl()
extension RequiresDataManager {
    var dataManager:DataManager {
        return theDataManager
    }
}
fileprivate let theLog = LogImpl()
extension RequiresLog {
    var log:Log {
        return theLog
    }
}

ClassThatUsesDataManager.swift

This shows the use of the Data Manager. Simply by declaring that the class implements the RequiresDataManager protocol, we automatically get access to a dataManager property on self. We don’t need to care whether that property is the real implementation or a mock version. Furthermore, we can later change or swap out the implementation without having to change ClassThatUsesDataManager, as long as the public interface remains the same.

class ClassThatUsesDataManager : RequiresDataManager, RequiresLog {
    func doSomethingWithDataManager() {
        dataManager.save(item:"happiness")
        log.debug("I saved something!")
    }
}

AnotherClassThatUsesDataManager.swift

If you prefer not to have your dependencies injected into your classes’ top-level namespace, here’s an alternative approach. Instead of extending the requirement mixins directly, we use a nested class. All dependencies are stored in self.dependencies.

class AnotherClassThatUsesDataManager {
    class Dependencies : RequiresDataManager, RequiresLog { }
    let dependencies = Dependencies()

    func doSomethingWithDataManager() {
        dependencies.dataManager.save(item:"happiness")
        dependencies.log.debug("I saved something!")
    }
}

Real world use

Admittedly, separating a class’s public interface into a protocol involves extra work and development time. This applies especially in the early stages of a project when the public interface is not yet set in stone. You may end up making a change in two or more places — the protocol and the concrete implementation, and perhaps in the mock implementation. While good development practices and unit testing make for solid software and reduce debugging time in the long run, this can be a tough sell when under deadlines. We can address this challenge through project management and communication (important, but not covered here) or through technical approaches.

The time involved can be mitigated by implementing the pattern gradually. In the early stages, you may choose to hold off on defining a separate protocol, and simply refer to the class itself by name. For example, in the above code the class that is called DataManagerImpl could instead be simply called DataManager. Once the public interface is solidified and you’re ready to implement unit tests, it is simple to create the protocol and rename the class.

Another time-saver is to automate the tasks of separating the interface and implementation, and of creating mock versions of components. A quick google search for “swift 4 mocking tools” turns up several promising results. Since Swift is such a new language, that landscape has yet to fully populate, and I have not yet evaluated the available offerings, but it is worth investigating to see if any of them fit your needs.

Give it a try

As Swift is still a relatively new language, best practices are still evolving, and part of the challenge is integrating the modern language features into the legacy Cocoa and Cocoa Touch platforms. The Dependency Mixin pattern has proven useful to me, and I hope you will find it helpful as well. Give it a “shot” and let me know what you think. As always, I am open to suggestions on cleaner ways to get things done.

%d bloggers like this: