Introduction of Dependency Inversion on Swift

Introduction of Dependency Inversion on Swift



Hmm interesting topic. The D of SOLID. No doubt SOLID has been a major development principle among the developer. And why not? It actually helps us greatly to make our code more agile. Which ultimately leads to more flexible code thus adapting beautifully with the latest requirement changes. On some future post series we will cover SOLID. But on this blog post we will only cover the Introduction of Dependency Inversion on Swift. Now why are we talking about dependency Inversion in mid of our Inheritance to composition talk series? Because on Swift the Dependency Inversion is achieved through protocol. And having a clear cut idea on Dependency Inversion can come up very handy when we are achieving composition through protocol. Combining these two concept can reduce the code coupling in a great deal.

The Swift Protocol Hub contains all the related posts with protocol. Be sure to check that out.

The introduction of Dependency Inversion Principle aka DIP in this blog post is highly sourced from Uncle bob, Robert C Martin,’s DIP episode.

Background

Introduction of Dependency Inversion

In its simplest term, dependency inversion is the process of reversing the source code dependency with respect to run time dependency.

Ahh ok it may not seems to be that much simpler, I guess. So need to dig a little bit more to understand the concept.

First we need to understand what does it actually mean when we say Dependency. What are the types? How those types are related?

Thats why on this blog post, we will have a Introduction of Dependency Inversion mainly on Swift

What does Dependency actually mean

Say we have a cool laptop with little RAM, does it help at all? The overall performance is dependent on RAM. Similarly an external drive have to implement the common I/O protocol to connect to that laptop or to the Operating System. The RAM is the dependency of that laptop. On the other hand the I/O protocol of OS is a dependency of the external drive.

Same way our app is dependent on the iOS by various occasion, to receive the tap gesture or the shake gesture or even the dismissal of our app from the screen.

Our beloved source code is also not independent, right? Often our code depends on the other source code written by us or by someone else. We define Car class which is dependent on Engine class. We fire an Internet Unavailability alert when we get a notification from the iOS that the Internet is unreachable.

Dependency is everywhere. The last example of source code is a source code dependency and above two are the runtime dependency. For this blog post we will only consider two types of dependency here.

  • Run time dependency
  • Compile time dependency or Source code dependency

When dependency occur on source code

Before starting the talk on dependency further more, we have to understand what actually a dependency is in code level?

  • When a Type is derived from another Type. That is a typical Inheritance.
  • When a Type confirms a protocol. That is a composition.
  • When Type A has an instance variable of Type B, then Type A has a dependency on the B Type.

Can we recall, how an app transforms from source code to an executable app? There are multiple phases but we will concentrate only on three; Building, Linking and Running. Even on Swift there are some more under the hood. I will suggest to have a look on the  Behind the Scenes of the Xcode Build Process talk of WWDC ’18 over this topic, when you have some time.

Now let us have a look on how the dependencies are resolved on those three phases.

On the very first phase that is the Building process, all the dependency are marked but not linked to the source code. So the existence of each of the dependencies of our source code are checked. As a result the references of different modules including third party’s source code are checked for availability.

On the second phase, Linking, the corresponding dependencies are linked with our source code. That mean the modular code is now put on with the source code to make a continuous code.

After completing some other phases the code is now a byte code or a machine code. So it is now an executable one. After this time there will be only, run time dependency. So if we have a hardware dependency, like shake or touch gesture, thats a run time dependency. Even an API call either to the system or to an external API provider is also a run time dependency.

Explanation

From now on when ever we say source code dependency or compile time dependency on this blog post, we will actually mean the Building and Linking phases dependencies.

Interestingly when we say run time dependency we will not only target the dependencies those are resolved on the Running phase but also the dependencies which are resolved by dynamic dispatching. Let us have an explanation.

Say we have a Type A which has a function reference or function call of function() on its scope. That function is declared on a protocol named P. And Type B implements that function(), so Type B is defining the function.
Now let us say A has a reference of P , let p: P, it calls the function as p.function(). On the runtime the p.function() will be dynamically dispatched to the function() implementation of B.
So A has a compile time dependency on P. And has a run time dependency to B.

Now we will talk about details on the Run time dependency and Compile time dependency.

Run time dependency

Run time dependency is also known as flow of control dependency or control flow dependency. When the dependency is visible on run time, thats a run time dependency.

Most often we see the UIApplicationMain attribute, as @UIApplicationMain, on our Swift project more specifically on AppDelegate.swift. This @UIApplicationMain attribute actually starts a flow, A flow of control. How?

When the @UIApplicationMain attribute is marked then, a new application-object is initiated by the system, iOS. On the creation time the AppDelegate name is passed as a parameter. This AppDelegate now becomes the application state observer. Moving to background or coming back from background are the examples of application state.
On the other hand the newly created application-object will handle the Application’s life cycle. Also by the application-object creation time, the iOS initiate an event-loop, which trace the user gesture on the window and pass that event to the application-object.

App Dependency control event
Control Flow

On the above diagram we can see the various run time dependencies. Very first the app initialization is dependent on the iOS. Then the app state is also dependent on iOS. Also the app life cycle is dependent on iOS. The touch event is dependent on the event-loop. All those dependencies are visible on run time. So they all are Run time dependency.

source code dependency

We just saw the run time dependency; which moves us to our next query, source code dependencyor compile time dependency.

So those dependency which are resolved on compile time is the compile time or source code dependency.

Very simply we can say: when a Type confirms a protocol ,thats a compile time dependency or source code dependency. Why? Because to compile that Type, the protocol need to compiled first. So on compilation time that Type is directly dependent on the protocol.

Similar situation arises when a Type is inherited from another Type. On compilation time the derived or the child class is directly dependent on its Parent class compilation.

Let us have an example on to find out the dependencies on a much more granule level, on code level.

Identifying dependency on code level

To make our code Agile we usually make our code base modular. So there is always different components interact with each other. We only want to focus on that modular part and how the dependency is laid out. We will visualize the dependency based on the three relationship that we discussed on the Dependency occurrence section.

Say we have a separated library of Avengers of Marvel, on reality we put the code on AvengersLib.swift on the DIP/Sources/AvengersLib.swift directory of the playground project dedicated to this blog post. We have the following definition on that file.

public protocol Avenger{
    var name: String {get}
    var speciality: String {get}
}

public struct IronMan: Avenger{
    public var name = "Iron Man"
    public var speciality = "Iron Man got the coolest gadgets. Techie and smart 😎"
    
    public init(){}
}

public struct CaptainAmerica: Avenger{
    public var name = "Captain America"
    public var speciality = "Super Human power. Quick to heal, tough to beat. 💪"
    
    public init(){}
}

public struct Hulk: Avenger{
    public var name = "Hulk"
    public var speciality = "God like power. Good at smashing  🔥"
    
    public init(){}
}

public struct Thor: Avenger{
    public var name = "Thor"
    public var speciality = "God of Thunder ⚡️"
    
    public init(){}
}

As we can see we have two property for the Avenger protocol. And each of the confirming type; IronMan, CaptainAmerica, Hulk, Thor is providing those two var. We have to give the public access to the property, even the init, to make things workable on the playground importing issue. However we are not dependent on the public keyword for this dependency talk 😉.

We will use the above code section to build two case study and will find out the dependencies. We will also graphically represent easing the understanding.

Case study of same dependency on run and compile time

So one the first case study we will look for an example where the compile and run time dependency is same. Say we have a base class, Team. And the TeamAvenger inherit from the Team class plus this class has instance variable of IronMan, CaptainAmerica, Hulk, Thor.

class Team{
    func introduction(){fatalError("Subclass should implement")}
}

class TeamAvenger: Team{
    private let initialTeam: [Avenger]
    
    private let ironMan: IronMan
    private let captainAmerica: CaptainAmerica
    private let hulk: Hulk
    private let thor: Thor
    
    override init() {
        ironMan = IronMan()
        captainAmerica = CaptainAmerica()
        hulk = Hulk()
        thor = Thor()
         
        initialTeam = [ironMan, captainAmerica, hulk, thor]
    }
    
    override func introduction() {
        initialTeam.forEach({print($0.name + ": " + $0.speciality)})
    }
}

let team = TeamAvenger()
team.introduction()

Our program first execute the let team = TeamAvenger() and then team.introduction(), which will print the name and speciality from the initialTeam array. And the initialTeam contains the instance variables of Avengers.

Now the important thing is, there is no dynamical dispatching here. So every this is pretty straight. We will have the dependency graph same for run and compile time.

Introduction of Run and compile time Dependency with and without Protocol
Run and compile time dependency

As we can see the TeamAvenger has a direct run and compile time dependency to Team as TeamAvenger is a subclass of Team.

Then the TeamAvenger has instances of IronMan, CaptainAmerica, Hulk and Thor. So all of those avengers are also the run and compile time dependency for TeamAvenger.

And finally all the ironMan, captainAmerica, hulk, thor have run and compile time dependency to Avengers as they are conforming the Avenger protocol.

Case study of different dependency on run and compile time

On this section we will have another case study. We will use the same Avengers from DIP/Sources/AvengersLib.swift. But this time we will have the SimpleAvenger class, which still inherit Team class. But this time we will only have an array of Avengers. And we will populate that with different Avenger type. So let us have some code.

class SimpleAvenger: Team{
    private let initialTeam: [Avenger]
    
    override init() {
        initialTeam = [IronMan(), CaptainAmerica(), Hulk(), Thor()]
    }
    
    override func introduction() {
        initialTeam.forEach({print($0.name + ": " + $0.speciality)})
    }
}

let simpleTeam = SimpleAvenger()
simpleTeam.introduction()

Now on this example we can see some dynamic dispatching. On the assigning statement of initialTeam on the init(), we can see different Avenger confirming types such as IronMan, CaptainAmerica, Hulk and Thor are added to the initialTeam which itself is an Avenger array type.

As a result we will have two different graphical representation of source code or compile time dependency and run time dependency.

So on compile time the SimpleAvenger is dependent on Avenger. But on run time SimpleAvenger is dependent on IronMan, CaptainAmerica, Hulk and Thor.

Though the SimpleAvenger have the run time dependency on IronMan, CaptainAmerica, Hulk and Thor but does not have compile time dependency on them.

Let visualize.

On compile time the SimpleAvenger has dependency on Team and Avenger.

Source code dependency of SimpleAvenger
SimpleAvenger Source Code Dependency

But on run time the SimpleAvenger has dependency on IronMan, CaptainAmerica, Hulk and Thor.

Run time dependency of SimpleAvenger, through dynamic dispatch
SimpleAvenger Run time Dependency

End Talk

Dependency Inversion seems to be a very easy topic unless touched, It is Swift or not doesn’t matter. On this Introduction of Dependency Inversion on Swift blog post we were only able to talk about the introductory part. We discussed how we can identify the dependencies and what are those two major part, run time and compile time dependencies, we have to count for.

Oh boy its not finish yet. On the next blog post we will discuss about achieving dependency inversion through protocol on Swift. And after that how we can use our these two blog post to make the dependency injection. Lot to talk about. So stay tunes.

Resources

2 thoughts on “Introduction of Dependency Inversion on Swift

Leave a Reply

Notifications for mobidevtalk! Cool ;) :( not cool