Skip to content

Commit

Permalink
Refactor WhoopDI to leverage container internally (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
ncipollo authored Oct 15, 2024
1 parent a7a1444 commit efd1916
Show file tree
Hide file tree
Showing 2 changed files with 19 additions and 63 deletions.
65 changes: 7 additions & 58 deletions Sources/WhoopDIKit/WhoopDI.swift
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
import Foundation
public final class WhoopDI: DependencyRegister {
nonisolated(unsafe) private static let serviceDict = ServiceDictionary<DependencyDefinition>()
nonisolated(unsafe) private static var localServiceDict: ServiceDictionary<DependencyDefinition>? = nil
nonisolated(unsafe) private static let appContainer = Container()

/// 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 static func registerModules(modules: [DependencyModule]) {
modules.forEach { module in
module.defineDependencies()
module.addToServiceDictionary(serviceDict: serviceDict)
}
appContainer.registerModules(modules: modules)
}

/// Injects a dependecy into your code.
///
/// The injected dependecy 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 static 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)")
}
appContainer.inject(name, params)
}

/// Injects a dependency into your code, overlaying local dependencies on top of the object graph.
Expand Down Expand Up @@ -51,62 +41,21 @@ public final class WhoopDI: DependencyRegister {
public static func inject<T>(_ name: String? = nil,
params: Any? = nil,
_ localDefiniton: (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()
localDefiniton(localModule)
localModule.addToServiceDictionary(serviceDict: serviceDict)

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

/// Used internally by the DependencyModule get to loop up a sub-dependency in the object graph.
internal static 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))
}
try appContainer.get(name, params)
}

/// 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).
internal static 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 static func getDefinition(_ serviceKey: ServiceKey) -> DependencyDefinition? {
return localServiceDict?[serviceKey] ?? serviceDict[serviceKey]
appContainer.validate(paramsDict: paramsDict, onFailure: onFailure)
}

public static func removeAllDependencies() {
serviceDict.removeAll()
appContainer.removeAllDependencies()
}
}
17 changes: 12 additions & 5 deletions Tests/WhoopDIKitTests/Module/DependencyModuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ class DependencyModuleTests: XCTestCase {
let defintion = serviceDict[serviceKey]
XCTAssertTrue(defintion is FactoryDefinition)
}


func test_get_missingContainer_fallsBackOnAppContainer() throws {
WhoopDI.registerModules(modules: [GoodTestModule()])
let dependencyC: DependencyC = try module.get(params: "params")
XCTAssertNotNil(dependencyC)
WhoopDI.removeAllDependencies()
}

func test_factoryWithParams() {
module.factoryWithParams(name: "name") { (_: Any) in "dependency" }
module.addToServiceDictionary(serviceDict: serviceDict)
Expand All @@ -40,12 +47,12 @@ class DependencyModuleTests: XCTestCase {
XCTAssertTrue(defintion is SingletonDefinition)
}

func test_ServiceKey_Returns_Subclass_Type() {
func test_serviceKey_Returns_Subclass_Type() {
let testModule = TestDependencyModule(testModuleDependencies: [])
XCTAssertEqual(testModule.serviceKey, ServiceKey(type(of: TestDependencyModule())))
}

func test_SetMultipleModuleDependencies() {
func test_setMultipleModuleDependencies() {
let moduleA = DependencyModule()
let moduleB = DependencyModule()
let moduleC = DependencyModule()
Expand All @@ -55,14 +62,14 @@ class DependencyModuleTests: XCTestCase {
XCTAssertEqual(module.moduleDependencies, [moduleD, moduleC, moduleB, moduleA])
}

func test_SetSingleModuleDependency() {
func test_setSingleModuleDependency() {
let moduleA = DependencyModule()

let module = TestDependencyModule(testModuleDependencies: [moduleA])
XCTAssertEqual(module.moduleDependencies, [moduleA])
}

func test_SetNoModuleDependencies() {
func test_setNoModuleDependencies() {
let module = TestDependencyModule()
XCTAssertEqual(module.moduleDependencies, [])
}
Expand Down

0 comments on commit efd1916

Please sign in to comment.