Skip to content

Commit

Permalink
Add initial container
Browse files Browse the repository at this point in the history
  • Loading branch information
ncipollo committed Oct 14, 2024
1 parent 577017c commit 63ae525
Showing 1 changed file with 114 additions and 0 deletions.
114 changes: 114 additions & 0 deletions Sources/WhoopDIKit/Container/Container.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import Foundation
public final class Container {
private let serviceDict = ServiceDictionary<DependencyDefinition>()
private var localServiceDict: ServiceDictionary<DependencyDefinition>? = nil

/// Registers a list of modules with the DI system.
/// Typically you will create a `DependencyModule` for your feature, then add it to the module list provided to this method.
public func registerModules(modules: [DependencyModule]) {
modules.forEach { module in
module.defineDependencies()
module.addToServiceDictionary(serviceDict: serviceDict)
}
}

/// Injects a dependency into your code.
///
/// The injected dependency will have all of it's sub-dependencies provided by the object graph defined in WhoopDI.
/// Typically this should be called from your top level UI object (ViewController, etc). Intermediate components should rely upon constructor injection (i.e providing dependencies via the constructor)
public func inject<T>(_ name: String? = nil, _ params: Any? = nil) -> T {
do {
return try get(name, params)
} catch {
print("Inject failed with stack trace:")
Thread.callStackSymbols.forEach { print($0) }
fatalError("WhoopDI inject failed with error: \(error)")
}
}

/// Injects a dependency into your code, overlaying local dependencies on top of the object graph.
///
/// The injected dependency will have all of it's sub-dependencies provided by the object graph defined in WhoopDI.
/// Typically this should be called from your top level UI object (ViewController, etc). Intermediate components should rely
/// upon constructor injection (i.e providing dependencies via the constructor).
///
/// This variant allows you to provide a local module definition which can be used to supply local dependencies to the object graph prior to injection.
///
/// Example:
/// ```swift
/// let localDependency = MyLocalDependency()
/// let myObject: MyObject = WhoopDI.inject { module in
/// module.factory { localDependency as LocalDependency }
/// }
/// ```
///
/// - Parameters:
/// - name: An optional name for the dependency. This can help disambiguate between dependencies of the same type.
/// - params: Optional parameters which will be provided to dependencies which require them (i.e dependencies using defintiions such as
/// (factoryWithParams, etc).
/// - localDefinition: A local module definition which can be used to supply local dependencies to the object graph prior to injection.
/// - Returns: The requested dependency.
public func inject<T>(_ name: String? = nil,
params: Any? = nil,
_ localDefinition: (DependencyModule) -> Void) -> T {
guard localServiceDict == nil else {
fatalError("Nesting WhoopDI.inject with local definitions is not currently supported")
}
// We need to maintain a reference to the local service dictionary because transient dependencies may also
// need to reference dependencies from it.
// ----
// This is a little dangerous since we are mutating a static variable but it should be fine as long as you
// don't use `inject { }` within the scope of another `inject { }`.
let serviceDict = ServiceDictionary<DependencyDefinition>()
localServiceDict = serviceDict
defer {
localServiceDict = nil
}

let localModule = DependencyModule()
localDefinition(localModule)
localModule.addToServiceDictionary(serviceDict: serviceDict)

do {
return try get(name, params)
} catch {
fatalError("WhoopDI inject failed with error: \(error)")
}
}

/// Used internally by the DependencyModule get to loop up a sub-dependency in the object graph.
func get<T>(_ name: String? = nil,
_ params: Any? = nil) throws -> T {
let serviceKey = ServiceKey(T.self, name: name)
let definition = getDefinition(serviceKey)
if let value = try definition?.get(params: params) as? T {
return value
} else if let injectable = T.self as? any Injectable.Type {
return try injectable.inject() as! T
} else {
throw DependencyError.missingDependecy(ServiceKey(T.self, name: name))
}
}

/// Used internally via the `WhoopDIValidator` to verify all definitions in the object graph have definitions for their sub-dependencies (i.e this verifies the object graph is complete).
func validate(paramsDict: ServiceDictionary<Any>, onFailure: (Error) -> Void) {
serviceDict.allKeys().forEach { serviceKey in
let definition = getDefinition(serviceKey)
do {
let _ = try definition?.get(params: paramsDict[serviceKey])
} catch {
onFailure(error)
}
}
}

private func getDefinition(_ serviceKey: ServiceKey) -> DependencyDefinition? {
return localServiceDict?[serviceKey] ?? serviceDict[serviceKey]
}

public func removeAllDependencies() {
serviceDict.removeAll()
}
}


0 comments on commit 63ae525

Please sign in to comment.