From 1fa924402acba9bcd041028fec7f1d3b87e2a525 Mon Sep 17 00:00:00 2001 From: Nick Cipollo Date: Tue, 15 Oct 2024 09:49:39 -0400 Subject: [PATCH] Refactor WhoopDI to leverage container internally --- Sources/WhoopDIKit/WhoopDI.swift | 65 ++----------------- .../Module/DependencyModuleTests.swift | 17 +++-- 2 files changed, 19 insertions(+), 63 deletions(-) diff --git a/Sources/WhoopDIKit/WhoopDI.swift b/Sources/WhoopDIKit/WhoopDI.swift index e08279a..c6c6855 100644 --- a/Sources/WhoopDIKit/WhoopDI.swift +++ b/Sources/WhoopDIKit/WhoopDI.swift @@ -1,15 +1,11 @@ import Foundation public final class WhoopDI: DependencyRegister { - nonisolated(unsafe) private static let serviceDict = ServiceDictionary() - nonisolated(unsafe) private static var localServiceDict: ServiceDictionary? = 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. @@ -17,13 +13,7 @@ public final class WhoopDI: DependencyRegister { /// 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(_ 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. @@ -51,62 +41,21 @@ public final class WhoopDI: DependencyRegister { public static func inject(_ 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() - 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(_ 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, 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() } } diff --git a/Tests/WhoopDIKitTests/Module/DependencyModuleTests.swift b/Tests/WhoopDIKitTests/Module/DependencyModuleTests.swift index 4cd75de..d0f0f1c 100644 --- a/Tests/WhoopDIKitTests/Module/DependencyModuleTests.swift +++ b/Tests/WhoopDIKitTests/Module/DependencyModuleTests.swift @@ -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) @@ -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() @@ -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, []) }