Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor WhoopDI to leverage container internally #15

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not related to the thread-safety stuff we had talked about before right? Since this is still the reason for the crashes I believe

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct - I'm just getting some infra work out of the way so I don't need to fix the thread safety issue twice.


/// 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
Loading