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 Dependency Inversion
- What does Dependency actually mean
- When dependency occur on source code
- Build Link Run
- run time dependency
- source code dependency
- Identifying dependency on code level
- Case study of same dependency on run and compile time
- Case study of different dependency on run and compile time
- End Talk
- Resources
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 TypeB
, then TypeA
has a dependency on theB
Type.
Build Link Run
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.
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.
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
.
But on run time the SimpleAvenger
has dependency on IronMan
, CaptainAmerica
, Hulk
and Thor
.
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
- Introduction of dependency inversion on swift Source code
- DIP by Uncle Bob
- Files and Initialization on Swift
- The App Delegate Source File section of Start Developing iOS Apps(Swift)
- The main run loop
- List of Swift attributes
- WWDC: Behind the Scenes of the Xcode Build Process
- Definition of different terms
- Dealing with Software dependency, audience c++
- Swift compiler and standard library design
- The sequence is drawn on sequencediagram
2 thoughts on “Introduction of Dependency Inversion on Swift”