Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.

Commit b0011e6

Browse files
committed
New post 🚀
1 parent a0a6525 commit b0011e6

File tree

2 files changed

+163
-31
lines changed

2 files changed

+163
-31
lines changed
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
---
2+
layout: post
3+
title: "How to: create your SUPER simple dependency injector framework in Swift"
4+
description: "There are a lot of dependency injection framework in the open source swift world with really cool features like object graph, persistence etc. But what if all you need is a lightweight dependencies container? In this post I will show you how to create it by leveraging the Metatype Type, the Hashable protocol and the Equatable protocol."
5+
date: 2020-03-19
6+
image: /assets/images/posts/XXXXXXXXXXXXX
7+
tags: [swift, ios, apple, mobile application development]
8+
comments: true
9+
math: false
10+
seo:
11+
- type: "BlogPosting"
12+
authors: [fabrizio_duroni]
13+
---
14+
15+
*There are a lot of dependency injection framework in the open source swift world with really cool features like object graph, persistence etc. But what if all you need is a lightweight dependencies container? In this post I will show you how to create it by leveraging the Metatype Type and the Hashable protocol and the Equatable protocol.*
16+
17+
---
18+
19+
The open source Swift world is full of useful framework. You can find almost everything you need (there are [rare cases where you need to write something that still doesn't exist](XXX id3 tag editor) out there). Anyway, a lot of the frameworks and libraries you will find out do more than you need. See for example the world of the dependecies injection framework. We have a lot of alternatives from which we can choose: [Swinject](https://github.com/Swinject/Swinject "dependecies injection swift Swinject"), [Weaver](https://github.com/scribd/Weaver "dependecies injection swift Weaver") etc. This frameworks come with a lot of features like: object graph construction, injection with property wrappers, instance persistence etc. This are all useful feature, but if your needs are very limited (just a dependecies container register/resolver using protocol and classes) the previous frameworks gives you just a big overhead and complexity on you code. This is why for my recent project I tried to write my own very simple dependencies injector container by leveraging the power of Swift Metatype and the Hashable protocol. Let's go and see the how I created it :smirk:.
20+
21+
#### Implementation
22+
23+
The main class of dependencies injector container is composed by 2 classes: `DependeciesContainer` and `DependencyKey`.
24+
The main one stores in the field `dependecies` a dictionary of all the dependencies registered using an instance of the second one. `DependeciesContainer` exposes two methods:
25+
26+
* `register<T>(type: T.Type, name: String? = nil, service: Any)`, that lets you register a new dependency in the container. It accepts 3 parameter. The first one is the `Type` of the dependency. The second one is a string that let's you identify different named variations of the same dependencies (e.g. you register `Cat` and `Dog`, two different implementation of an hypothetical `Animal` protocol). The 3 one is the instance saved in the `dependecies` dictionary.
27+
28+
* `resolve<T>(type: T.Type, name: String? = nil) -> T?`, that lets you get an instance previously registered. This method accept the same first two parameter of the previous method. It will return null if none of the registered instance has a combination of `type` and `name` as the one received as parameters.
29+
30+
This is the implementation of `DependeciesContainer`.
31+
32+
```swift
33+
class DependeciesContainer {
34+
static let shared = DependeciesContainer()
35+
36+
private init() {}
37+
38+
private var dependecies: [DependencyKey : Any] = [:]
39+
40+
func register<T>(type: T.Type, name: String? = nil, service: Any) {
41+
let dependencyKey = DependencyKey(type: type, name: name)
42+
dependecies[dependencyKey] = service
43+
}
44+
45+
func resolve<T>(type: T.Type, name: String? = nil) -> T? {
46+
let dependencyKey = DependencyKey(type: type, name: name)
47+
return dependecies[dependencyKey] as? T
48+
}
49+
}
50+
```
51+
52+
Now, you can ask some question after seeing this code. First of all, what is a `Type`? Apple [defines it](https://docs.swift.org/swift-book/ReferenceManual/Types.html#grammar_metatype-type "swift metatype") as:
53+
54+
> A metatype type refers to the type of any type, including class types, structure types, enumeration types, and protocol types.
55+
56+
A metatype is the representation of the type of an instance. It allows you to use all of class properties and methods of a type. It allows also to threat your code as a data. In this specific case we are using the `Type` metatype to identify an instance from its own description.
57+
The second one question that you could ask is: how can you use the `DependecyKey` class instances as key in your `dependecies` dictionary? This is possible thanks to the `Hashable` and `Equatable` protocols. This class implements the `hash(into hasher: inout Hasher)` method of the `Hashable` protocol by using the combination of `type` and `name` received from the `DependeciesContainer`. In that method I'm putting in the same hasher the `type` and the `name`. In particular for the first one I'm extracting a unique identifier using the `ObjectIdentifier` function from the Swift `Reflection` package.
58+
It also implements the `Equatable` protocol using the same fields in the `static func == (lhs: DependencyKey, rhs: DependencyKey) -> Bool` method. By implementing this two protocol a class could be used as key in a dictionary.
59+
60+
```swift
61+
class DependencyKey: Hashable, Equatable {
62+
private let type: Any.Type
63+
private let name: String?
64+
65+
init(type: Any.Type, name: String? = nil) {
66+
self.type = type
67+
self.name = name
68+
}
69+
70+
func hash(into hasher: inout Hasher) {
71+
hasher.combine(ObjectIdentifier(type))
72+
hasher.combine(name)
73+
}
74+
75+
static func == (lhs: DependencyKey, rhs: DependencyKey) -> Bool {
76+
return lhs.type == rhs.type && lhs.name == rhs.name
77+
}
78+
}
79+
```
80+
81+
This is all I need for my ultra light dependencies injector :heart_eyes:!!! Let's see it in action in an example.
82+
83+
```swift
84+
protocol Animal {
85+
func isAlive() -> Bool
86+
}
87+
88+
class Cat: Animal {
89+
func isAlive() -> Bool {
90+
true
91+
}
92+
93+
func miaow() -> String {
94+
return "miaow"
95+
}
96+
}
97+
98+
class Dog: Animal {
99+
func isAlive() -> Bool {
100+
true
101+
}
102+
103+
func bark() -> String {
104+
return "wooof"
105+
}
106+
}
107+
108+
class Tiger: Animal {
109+
func isAlive() -> Bool {
110+
true
111+
}
112+
113+
func roar() -> String {
114+
return "roar"
115+
}
116+
}
117+
118+
119+
protocol Person {
120+
func breath() -> String
121+
}
122+
123+
class SickPerson: Person {
124+
func breath() -> String {
125+
return "fiuu"
126+
}
127+
128+
func cough() -> String {
129+
return "cough"
130+
}
131+
}
132+
133+
let dc = DependeciesContainer.shared
134+
135+
// Register using class `Type` Dog
136+
dc.register(type: Dog.self, service: Dog())
137+
let dog = dc.resolve(type: Dog.self)!
138+
print(String(describing: dog)) // "__lldb_expr_7.Dog\n"
139+
print(dog.bark()) // "wooof\n"
140+
141+
// Register using protocol `Type` Person
142+
dc.register(type: Person.self, service: SickPerson())
143+
let person = dc.resolve(type: Person.self)!
144+
print(String(describing: person)) // "__lldb_expr_7.SickPerson\n"
145+
print(person.breath()) // "fiuu\n"
146+
print((person as! SickPerson).cough()) // "cough\n"
147+
148+
// Register using protocol `Type` Animal and variations
149+
dc.register(type: Animal.self, name: "Cat", service: Cat())
150+
dc.register(type: Animal.self, name: "Tiger", service: Tiger())
151+
let cat = dc.resolve(type: Animal.self, name: "Cat")!
152+
print(String(describing: cat))
153+
print(cat.isAlive())
154+
print((cat as! Cat).miaow())
155+
let tiger = dc.resolve(type: Animal.self, name: "Tiger")!
156+
print(String(describing: tiger))
157+
print(tiger.isAlive())
158+
print((tiger as! Tiger).roar())
159+
```
160+
161+
#### Conclusion
162+
163+
You can find all the code shown in this post [in this Github repo](https://github.com/chicio/XXX "custom tab bar swiftui"). Remember: sometimes with a couple of classes you can avoid to import big frameworks and library into your projects. You just need to study hard the languages and SDK fundamental :heartpulse:.

_drafts/2020-03-25-dependecy-injection-swift.md

Lines changed: 0 additions & 31 deletions
This file was deleted.

0 commit comments

Comments
 (0)