Skip to content

Instantly share code, notes, and snippets.

@dcarmo-tribalscale
Last active March 3, 2023 22:24
Show Gist options
  • Select an option

  • Save dcarmo-tribalscale/cf0713921b7f79f2f26bedc92d45de0d to your computer and use it in GitHub Desktop.

Select an option

Save dcarmo-tribalscale/cf0713921b7f79f2f26bedc92d45de0d to your computer and use it in GitHub Desktop.
Dependency Injection Blog
// 1
public protocol DIContainer {
func register<DependencyType>(type: DependencyType.Type, dependency: DependencyType)
func resolve<DependencyType>(type: DependencyType.Type) -> DependencyType
}
// 2
public class DependencyContainer {
public static let shared: DIContainer = DependencyContainer()
private var registeredDependencies: [String: Any] = [:]
}
// 3
extension DependencyContainer: DIContainer {
public func register<DependencyType>(type: DependencyType.Type, dependency: DependencyType) {
let key = dependencyKey(for: type)
registeredDependencies[key] = dependency
}
public func resolve<DependencyType>(type: DependencyType.Type) -> DependencyType {
let key = dependencyKey(for: type)
guard let dependency = registeredDependencies[key] as? DependencyType else {
preconditionFailure("DependencyContainer.resolve. There is no dependency registered for this type. Please register a dependency for this type.")
}
return dependency
}
private func dependencyKey<DependencyType>(for type: DependencyType.Type) -> String {
String(describing: type)
}
}
// 1
DependencyContainer.shared.register(type: NetworkingLayer.self, dependency: AppNetworkingLayer())
class ViewController: UIViewController {
let networkingLayer: NetworkingLayer
// 2
init(networkingLayer: NetworkingLayer = DependencyContainer.shared.resolve(type: NetworkingLayer.self)) {
self.networkingLayer = networkingLayer
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
public protocol DIContainer {
func register<DependencyType>(type: DependencyType.Type, dependency: DependencyType)
// 1
func register<DependencyType>(key: String, dependency: DependencyType)
func resolve<DependencyType>(type: DependencyType.Type) -> DependencyType
// 2
func resolve<DependencyType>(key: String) -> DependencyType
}
public class DependencyContainer {
public static let shared: DIContainer = DependencyContainer()
private var registeredDependencies: [String: Any] = [:]
}
extension DependencyContainer: DIContainer {
public func register<DependencyType>(type: DependencyType.Type, dependency: DependencyType) {
register(key: dependencyKey(for: type), dependency: dependency)
}
// 3
public func register<DependencyType>(key: String, dependency: DependencyType) {
registeredDependencies[key] = dependency
}
public func resolve<DependencyType>(type: DependencyType.Type) -> DependencyType {
return resolve(key: dependencyKey(for: type))
}
// 4
public func resolve<DependencyType>(key: String) -> DependencyType {
guard let dependency = registeredDependencies[key] as? DependencyType else {
preconditionFailure("DependencyContainer.resolve. There is no dependency registered for this type. Please register a dependency for this type.")
}
return dependency
}
private func dependencyKey<DependencyType>(for type: DependencyType.Type) -> String {
String(describing: type)
}
}
@propertyWrapper
public struct Inject<Value> {
private(set) public var wrappedValue: Value
// 5
public init(key: String? = nil) {
// 6
if let key = key {
self.wrappedValue = DependencyContainer.shared.resolve(key: key)
} else {
self.wrappedValue = DependencyContainer.shared.resolve(type: Value.self)
}
}
}
DependencyContainer.shared.register(type: NetworkingLayer.self, dependency: AppNetworkingLayer())
// 1
DependencyContainer.shared.register(key: "MockedNetworkingLayer", dependency: MockNetworkingLayer())
class ViewControllerInjected: UIViewController {
// 2
@Inject
var networkingLayer: NetworkingLayer
// 3
@Inject(key: "MockedNetworkingLayer")
var mockedNetworkingLayer: NetworkingLayer
}
public protocol DIContainer {
// 1
func register<DependencyType>(type: DependencyType.Type, dependency: @escaping () -> DependencyType)
func register<DependencyType>(key: String, dependency: @escaping () -> DependencyType)
func resolve<DependencyType>(type: DependencyType.Type) -> DependencyType
func resolve<DependencyType>(key: String) -> DependencyType
}
public class DependencyContainer {
public static let shared: DIContainer = DependencyContainer()
// 2
private var dependencyInitializer: [String: () -> Any] = [:]
}
extension DependencyContainer: DIContainer {
// 3
public func register<DependencyType>(type: DependencyType.Type, dependency: @escaping () -> DependencyType) {
register(key: dependencyKey(for: type), dependency: dependency)
}
public func register<DependencyType>(key: String, dependency: @escaping () -> DependencyType) {
dependencyInitializer[key] = dependency
}
public func resolve<DependencyType>(type: DependencyType.Type) -> DependencyType {
return resolve(key: dependencyKey(for: type))
}
public func resolve<DependencyType>(key: String) -> DependencyType {
// 4
guard let dependency = dependencyInitializer[key]?() as? DependencyType else {
preconditionFailure("DependencyContainer.resolve. There is no dependency registered for this type. Please register a dependency for this type.")
}
return dependency
}
private func dependencyKey<DependencyType>(for type: DependencyType.Type) -> String {
String(describing: type)
}
}
@propertyWrapper
public struct Inject<Value> {
private(set) public var wrappedValue: Value
public init(key: String? = nil) {
if let key = self.key {
self.wrappedValue = DependencyContainer.shared.resolve(key: key)
} else {
self.wrappedValue = DependencyContainer.shared.resolve(type: Value.self)
}
}
}
// 1
DependencyContainer.shared.register(type: NetworkingLayer.self, dependency: {
AppNetworkingLayer()
})
DependencyContainer.shared.register(key: "MockedNetworkingLayer", dependency: {
MockNetworkingLayer()
})
DependencyContainer.shared.register(type: User.self, dependency: {
let cache = UserCache()
return cache.user
})
class ViewControllerInjected: UIViewController {
@Inject
var networkingLayer: NetworkingLayer
@Inject(key: "MockedNetworkingLayer")
var mockedNetworkingLayer: NetworkingLayer
// 2
@Inject
var user: User
}
// 1
public enum ResolveMode {
case new
case shared
}
public protocol DIContainer {
func register<DependencyType>(type: DependencyType.Type, dependency: @escaping () -> DependencyType)
func register<DependencyType>(key: String, dependency: @escaping () -> DependencyType)
// 2
func resolve<DependencyType>(type: DependencyType.Type, mode: ResolveMode) -> DependencyType
func resolve<DependencyType>(key: String, mode: ResolveMode) -> DependencyType
}
public class DependencyContainer {
public static let shared: DIContainer = DependencyContainer()
// 3
private var dependencyInitializer: [String: () -> Any] = [:]
// 4
private var dependencyShared: [String: Any] = [:]
}
extension DependencyContainer: DIContainer {
public func register<DependencyType>(type: DependencyType.Type, dependency: @escaping () -> DependencyType) {
register(key: dependencyKey(for: type), dependency: dependency)
}
public func register<DependencyType>(key: String, dependency: @escaping () -> DependencyType) {
dependencyInitializer[key] = dependency
}
public func resolve<DependencyType>(type: DependencyType.Type, mode: ResolveMode) -> DependencyType {
return resolve(key: dependencyKey(for: type), mode: mode)
}
public func resolve<DependencyType>(key: String, mode: ResolveMode) -> DependencyType {
// 5
switch mode {
case .new:
// 6
guard let newDependency = dependencyInitializer[key]?() as? DependencyType else {
preconditionFailure("DependencyContainer.resolve. There is no dependency registered for this type. Please register a dependency for this type.")
}
return newDependency
case .shared:
// 7
if dependencyShared[key] == nil, let dependency = dependencyInitializer[key]?() {
dependencyShared[key] = dependency
}
// 8
guard let sharedDependency = dependencyShared[key] as? DependencyType else {
preconditionFailure("DependencyContainer.resolve. There is no dependency registered for this type. Please register a dependency for this type.")
}
return sharedDependency
}
}
private func dependencyKey<DependencyType>(for type: DependencyType.Type) -> String {
String(describing: type)
}
}
@propertyWrapper
public struct Inject<Value> {
private(set) public var wrappedValue: Value
// 9
public init(key: String? = nil, mode: ResolveMode = .shared) {
// 10
if let key = key {
wrappedValue = DependencyContainer.shared.resolve(key: key, mode: mode)
} else {
wrappedValue = DependencyContainer.shared.resolve(type: Value.self, mode: mode)
}
}
}
DependencyContainer.shared.register(type: NetworkingLayer.self, dependency: {
AppNetworkingLayer()
})
DependencyContainer.shared.register(key: "MockedNetworkingLayer", dependency: {
MockNetworkingLayer()
})
DependencyContainer.shared.register(type: User.self, dependency: {
let cache = UserCache()
return cache.user
})
class ViewControllerInjected: UIViewController {
@Inject
var networkingLayer: NetworkingLayer
@Inject(key: "MockedNetworkingLayer")
var mockedNetworkingLayer: NetworkingLayer
// 1
@Inject(mode: .new)
var user: User
}
// 1
@propertyWrapper
// 2
public struct Inject<Value> {
private(set) public var wrappedValue: Value
public init() {
// 3
self.wrappedValue = DependencyContainer.shared.resolve(type: Value.self)
}
}
class ViewControllerInjected: UIViewController {
// 1
@Inject
var networkingLayer: NetworkingLayer
}
@propertyWrapper
public struct Inject<Value> {
private(set) public var wrappedValue: Value
// 1
public init(container: DIContainer = DependencyContainer.shared, key: String? = nil, mode: ResolveMode = .shared) {
if let key = key {
wrappedValue = container.resolve(key: key, mode: mode)
} else {
wrappedValue = container.resolve(type: Value.self, mode: mode)
}
}
}
let networkContainer: DIContainer = DependencyContainer()
class ViewControllerInjected: UIViewController {
@Inject
var networkingLayer: NetworkingLayer
@Inject(key: "MockedNetworkingLayer")
var mockedNetworkingLayer: NetworkingLayer
@Inject
var user: User
// 1
@Inject(container: networkContainer)
var fromNetworkContainer: NetworkingLayer
}
@propertyWrapper
public struct LazyInject<Value> {
// 1
private let container: DIContainer
private let key: String?
private let mode: ResolveMode
// 2
private(set) public lazy var wrappedValue: Value = {
if let key = self.key {
return self.container.resolve(key: key, mode: self.mode)
} else {
return self.container.resolve(type: Value.self, mode: self.mode)
}
}()
// 3
public init(container: DIContainer = DependencyContainer.shared, key: String? = nil, mode: ResolveMode = .shared) {
self.container = container
self.key = key
self.mode = mode
}
}
// 1
DependencyContainer.shared.register(type: NetworkingLayer.self, dependency: {
AppNetworkingLayer()
})
DependencyContainer.shared.register(type: UserCache.self, dependency: {
UserCache()
})
class UserCache {
// 2
@LazyInject
var network: NetworkingLayer
let user: User = User(firstName: "First",
lastName: "Last",
email: "first.last@gmail.com",
isPushEnabled: true,
isLocationServicesEnabled: false)
}
protocol NetworkingLayer: AnyObject {
func makeRequest()
}
class AppNetworkingLayer: NetworkingLayer {
// 3
@LazyInject
var user: UserCache
func makeRequest() {
print("AppNetworkingLayer made a request")
}
}
class ViewControllerInjected: UIViewController {
// 4
@Inject
var networkingLayer: NetworkingLayer
@Inject
var user: User
}
protocol NetworkingLayer {
func makeRequest()
}
class AppNetworkingLayer: NetworkingLayer {
func makeRequest() {
print("AppNetworkingLayer made a request")
}
}
class MockNetworkingLayer: NetworkingLayer {
func makeRequest() {
print("MockNetworkingLayer made a request")
}
}
import XCTest
@testable import YourProject
class ClassToTest: XCTestCase {
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
// 1
DependencyContainer.shared.register(type: NetworkingLayer.self, dependency: {
MockNetworkingLayer()
})
}
// Tests here
override func tearDown() {
// 2
DependencyContainer.shared.remove(type: NetworkingLayer.self)
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
}
@propertyWrapper
// 1
public struct WeakInject<Value> {
// 2
private weak var underlyingValue: AnyObject?
// 3
public var wrappedValue: Value? {
return underlyingValue as? Value
}
public init(container: DIContainer = DependencyContainer.shared, key: String? = nil, mode: ResolveMode = .shared) {
// 4
if let key = key {
self.underlyingValue = container.resolve(key: key, mode: mode)
} else {
self.underlyingValue = container.resolve(type: Value.self, mode: mode) as AnyObject
}
}
}
class ViewControllerInjected: UIViewController {
@Inject
var networkingLayer: NetworkingLayer
@Inject(key: "MockedNetworkingLayer")
var mockedNetworkingLayer: NetworkingLayer
@Inject
var user: User
@Inject(container: networkContainer)
var fromNetworkContainer: NetworkingLayer
// 1
@WeakInject
var networkingLayer2: NetworkingLayer?
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment