0

Injection

This package was developed to address one of the most challenging topics in the Swift community, dependency injection.

Injection

example workflow GitHub License SPM compatible

Table of Contents

  1. Introduction
  2. How to use
  3. Testing
  4. Resources
  5. Author

Introduction

This package was developed to address one of the most challenging topics in the Swift community: dependency injection. I often found it difficult to grasp this concept and implement it effectively. Through extensive research, I discovered a straightforward method inspired by the @Environment property wrapper in SwiftUI. A defining feature of this package is its simplicity, making it easily understandable for anyoney.

Key issues addressed by this package include:

  • Simplifying the process of mocking data for tests.
  • Maintaining readability by adhering closely to Swift’s standard APIs.
  • Ensuring compile-time safety to prevent hidden crashes, so if the application builds, all dependencies are correctly configured.
  • Avoiding large initializers caused by dependency injection.
  • Relieving the AppDelegate from being the central point for defining all shared instances.
  • Minimizing potential learning curves.
  • Eliminating the need for force unwrapping.
  • Allowing the definition of standard dependencies without exposing private or internal types within packages.

How to use

Let's say we have a protocol called MotorBike (why motorBike because I love them 😍😅) that contains a funcation called move:

protocol MotorBike {
    func move() -> Int
}

And there is a struct called Ducati that conforms to MotorBike:

struct Ducati: MotorBike {
   func move() -> Int {
       return 5
   }
}

At this point we want to inject this object to another object using dependency injection design pattern to do so, the first step is to create a key (like the @Environment):

private struct DucatiKey: InjectionKey {
   static var currentValue: MotorBike = Ducati()
}

Then you will add this key to the InjectedValues struct:

extension InjectedValues {
    var ducatiProvider: MotorBike {
       get { Self[DucatiKey.self] }
       set { Self[DucatiKey.self] = newValue }
   }
}

To be able to use it, you will just write this where ever you need it:

struct Garage {
@Injected(\.ducatiProvider) var ducati: MotorBike
}

And that's it, simple right 🚀.

[!NOTE]
When ever you call the @Injected you will have the same instance throught the life cycle of the app. To prevent any side effects and weird outcomes from happening due to inconsistent dependency references.

[!NOTE]
Plus you can easily change the instance by writing:

let garage = Garage()
garage.ducati = Ducati()
//OR
InjectedValues[\.ducatiProvider] = Ducati()

Using the InjectedValues static subscript also affects already injected properties.

Testing

Testing your code is more easier than you think, you will just have to change the injected properties with a mock one using InjectedValues static subscript, like so:

final class MotorBikeTests: XCTestCase {
    override func setUp() {
        InjectedValues[\.ducatiProvider] = DuctiMock()
    }
    
    func test() {
        let garge = Garage()
        
        let result = garge.move()
        
        XCTAssertEqual(10, result)
    }
}
 
class DuctiMock: MotorBike {
    func move() -> Int {
        return 10
    }
}

that's it for how to test your injected properties. Easey right ? 😎

Resources

Some helpful links for you:

Author

This pacakge was created by Eng.Omar Elsayed to help the iOS comuntity and make there life easir. To contact me email me at eng.omar.elsayed@hotmail.com