DEV Community

loading...

Dependency Inversion Principle in Swift

kevinmaarek profile image Maarek ・2 min read

You may have heard about the Dependency Inversion principle from the OOP’s SOLID design guidelines.
The idea behind this principle is that an implementation should only be dependant of interfaces and abstractions - never of another implementation.
It also brings us the concept that a higher level module should never be dependant of a lower level module.

Protocol to the rescue

In Swift, with protocols, you can get rid of any implementation dependency and instead rely on the implementation of a protocol.

Real life example

Let say you have a network stack :

class AppServices {
    // AppServices will keep an instance of network and any other lower stack
    // that must have a shared instance. Eg: Persistence.
    static let network: AppNetwork = AppNetwork()
}

class AppNetwork {
    // This class is in charge of auth, holds the token and requests queue
    func fetch(_ operation: NetworkOperation, completion: NetworkCompletion)
}
Enter fullscreen mode Exit fullscreen mode

And a controller that requests a list of data from an API, using the network stack :

class ViewController: UIViewController {
    func loadData() {
        AppServices.network.fetch(ListData) {
            self.view.refresh(listData)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The Network stack is a low level module, and the ViewController is tightly coupled with AppNetwork’s implementation throughout AppServices.

We can solve this dependency issue by recreating the Network as a protocol :

protocol Network {
    func fetch(_ operation: NetworkOperation, completion: NetworkCompletion) {}
}
Enter fullscreen mode Exit fullscreen mode

and make the AppNetwork conforms to Network :

class AppServices {
    static let network: Network = AppNetwork()
}

class AppNetwork: Network { … }
Enter fullscreen mode Exit fullscreen mode

Now, AppServices wont hold a AppNetwork implementation but an abstract implementation of Network.
Another benefit of this is you can easily mock your networks stack, simply by replacing the implementation of Network.

class MockedNetwork: Network {
    func fetch(_ operation: NetworkOperation, completion: NetworkCompletion) {
        completion(stubResult, fakeError)
    }
}
Enter fullscreen mode Exit fullscreen mode
// make sure you set the static as a `var` and not a `let`
AppService.network = MockedNetwork()
Enter fullscreen mode Exit fullscreen mode

Following this principle in this specific scenario is a very nice and clean way to improve the testability of any network dependant code, the flexibility and the scalability of your network stack, and you can remove any coupling of your app's controllers or view to your app's lower stacks.

I hope you enjoy this little post.
I chose to cover the dependency inversion principle as I feel it is a very useful and important principle to follow while coding and a great way to improve code and product's quality. I would be happy to answer any question in the comment section or feel free to reach via my Twitter ;)

Happy coding!

Discussion (0)

pic
Editor guide