diff --git a/README.md b/README.md index 7e15416..00f91ad 100644 --- a/README.md +++ b/README.md @@ -32,13 +32,13 @@ Grove simplifies dependency registration and resolving. Here's an example: let container = Grove.defaultContainer // You can use the default container or create your own // Register a reference type dependency as a singleton -container.register(JSONEncoder.init) +container.register(JSONEncoder()) // or with a transient lifetime -container.register(scope: .transient, JSONEncoder.init) +container.register(scope: .transient, JSONEncoder()) // Register a value type dependency (an enum for example) -container.register(value: DeploymentEnvironment.production) +container.register(DeploymentEnvironment.production) ``` ### Resolution @@ -75,7 +75,7 @@ final class DependenciesRegistrar { return decoder } - container.register(as: NASARepositoryProtocol.self, NASARepository.init) + container.register(as: NASARepositoryProtocol.self, NASARepository()) } static func registerMocks() { @@ -91,12 +91,12 @@ final class DependenciesRegistrar { return decoder } - container.register(as: NASARepositoryProtocol.self, MockNASARepository.init) + container.register(as: NASARepositoryProtocol.self, MockNASARepository()) } } ``` -You can then call DependenciesRegistrar.register() from your App's init() or AppDelegate. For unit tests or SwiftUI previews, you can call DependenciesRegistrar.registerMocks(). +You can then call `DependenciesRegistrar.register()` from your App's `init()` or `AppDelegate`. For unit tests or SwiftUI previews, you can call `DependenciesRegistrar.registerMocks()`. ## Contributing Contributions are immensely appreciated. Feel free to submit pull requests or to create issues to discuss any potential bugs or improvements. diff --git a/Sources/Grove/Grove.swift b/Sources/Grove/Grove.swift index 539db84..210bc8b 100644 --- a/Sources/Grove/Grove.swift +++ b/Sources/Grove/Grove.swift @@ -13,16 +13,18 @@ public final class Grove: @unchecked Sendable { /// Scope, or lifetime of a reference-type dependency public enum Scope { - // singleton: dependency is initialized once and then reused. Its lifetime is the lifetime of the container (the app in most cases). - // transient: dependency is initialized every time it is resolved. Its lifetime is the lifetime of the object that owns the dependency. - case singleton, transient + /// Dependency is initialized once and then reused. Its lifetime is the lifetime of the container (the app in most cases). + case singleton + /// Dependency is initialized every time it is resolved. Its lifetime is the lifetime of the object that owns the dependency. + case transient } private enum DependencyItem { case initializer(() -> Any, scope: Scope) case instance(Any) } - private var dependencyItemsMap = [String: DependencyItem]() + + private var dependencyItemsMap = [ObjectIdentifier: DependencyItem]() private let dependencyItemsMapLock = NSLock() /// Default container @@ -37,73 +39,65 @@ public final class Grove: @unchecked Sendable { /// - scope: Optional scope to use for registration: singleton or transient. Transient dependencies are initialized every time they are resolved /// - initializer: Initializer for the dependency to be registered (ex. JSONEncoder.init, or { JSONEncoder() }) /// - public func register(as type: T.Type = T.self, scope: Scope = .singleton, _ initializer: @escaping () -> T) { + public func register( + as type: Dependency.Type = Dependency.self, + scope: Scope = .singleton, + with initializer: @escaping () -> Dependency + ) { dependencyItemsMapLock.lock() - dependencyItemsMap[key(for: T.self)] = DependencyItem.initializer(initializer, scope: scope) + dependencyItemsMap[key(for: Dependency.self)] = .initializer(initializer, scope: scope) dependencyItemsMapLock.unlock() } - /// Registers using a value - /// - Parameters: - /// - type: Optional type of to use for registration - /// - value: Value for the dependency to be registered - /// - public func register(as type: T.Type = T.self, value: T) { - dependencyItemsMapLock.lock() - dependencyItemsMap[key(for: T.self)] = DependencyItem.instance(value) - dependencyItemsMapLock.unlock() + public func register( + as type: Dependency.Type = Dependency.self, + scope: Scope = .singleton, + _ initializer: @autoclosure @escaping () -> Dependency + ) { + register(as: type, scope: scope, with: initializer) } /// Returns the resolved dependency /// - Returns: The resolved dependency /// Example: `let jsonEncoder: JSONEncodingProtocol = Grove.defaultContainer.resolve()` /// - public func resolve() -> T { - let key = key(for: T.self) + public func resolve() -> Dependency { + let key = key(for: Dependency.self) + dependencyItemsMapLock.lock() let dependencyItem = dependencyItemsMap[key] dependencyItemsMapLock.unlock() + let dependency: Any + switch dependencyItem { case .initializer(let initializer, let scope): - let objectInstance = initializer() + dependency = initializer() switch scope { case .singleton: dependencyItemsMapLock.lock() - dependencyItemsMap[key] = DependencyItem.instance(objectInstance) + dependencyItemsMap[key] = .instance(dependency) dependencyItemsMapLock.unlock() case .transient: // No-Op break } - guard let objectInstance = objectInstance as? T else { - preconditionFailure("Grove: '\(key)' stored as '\(objectInstance.self)' (requested: '\(T.self)').") - } - return objectInstance case .instance(let instance): - guard let instance = instance as? T else { - preconditionFailure("Grove: '\(key)' stored as '\(instance.self)' (requested: '\(T.self)').") - } - return instance + dependency = instance case .none: preconditionFailure("Grove: '\(key)' Not registered.") } - } - - // MARK: Helpers - private func key(for type: T.Type) -> String { - let rawKey = String(describing: T.self) - if !rawKey.hasPrefix("Optional<") { - return rawKey - .replacingOccurrences(of: "Optional<", with: "") - .replacingOccurrences(of: ">", with: "") - } else { - return rawKey + guard let dependency = dependency as? Dependency else { + preconditionFailure("Grove: '\(key)' stored as '\(dependency.self)' (requested: '\(Dependency.self)').") } + + return dependency } - public func register(_ initializer: @escaping () -> T, scope: Scope = .singleton) { - register(as: T.self, scope: scope, initializer) + // MARK: Helpers + + private func key(for type: Dependency.Type) -> ObjectIdentifier { + ObjectIdentifier(type) } } diff --git a/Tests/GroveTests/GroveTests.swift b/Tests/GroveTests/GroveTests.swift index 7d3f82c..6a0e40d 100644 --- a/Tests/GroveTests/GroveTests.swift +++ b/Tests/GroveTests/GroveTests.swift @@ -22,7 +22,7 @@ final class GroveTests: XCTestCase { /// Tests registering a class and resolving it. func testDirectClassRegistration() { - Grove.defaultContainer.register(NotProtocolConformingTestClass.init) + Grove.defaultContainer.register(NotProtocolConformingTestClass()) let testClass: NotProtocolConformingTestClass = Grove.defaultContainer.resolve() @Resolve var testClass2: TestProtocol @@ -31,7 +31,7 @@ final class GroveTests: XCTestCase { /// Tests registering a class as a protocol and resolving it as a protocol. func testClassAsProtocolRegistration() { - Grove.defaultContainer.register(as: TestProtocol.self, TestClass.init) + Grove.defaultContainer.register(as: TestProtocol.self, TestClass()) @Resolve var testClass: TestProtocol XCTAssertEqual(testClass.value, 0) @@ -39,7 +39,7 @@ final class GroveTests: XCTestCase { /// Tests registering a class using the transient lifetime scope func testTransientScope() { - Grove.defaultContainer.register(as: TestProtocol.self, scope: .transient, TestClass.init) + Grove.defaultContainer.register(as: TestProtocol.self, scope: .transient, TestClass()) @Resolve var testClass1: TestProtocol testClass1.increment() @@ -53,7 +53,7 @@ final class GroveTests: XCTestCase { /// Tests registering a class using the singleton lifetime scope func testSingletonScope() { - Grove.defaultContainer.register(as: TestProtocol.self, scope: .singleton, TestClass.init) + Grove.defaultContainer.register(as: TestProtocol.self, scope: .singleton, TestClass()) @Resolve var testClass1: TestProtocol testClass1.increment() diff --git a/Tests/GroveTests/GroveValueTypeDependencyTests.swift b/Tests/GroveTests/GroveValueTypeDependencyTests.swift index e1389d9..97bf115 100644 --- a/Tests/GroveTests/GroveValueTypeDependencyTests.swift +++ b/Tests/GroveTests/GroveValueTypeDependencyTests.swift @@ -26,7 +26,7 @@ final class GroveValueTypeDependencyTests: XCTestCase { /// Tests registering a value type and resolving it. func testDirectTypeRegistration() { - Grove.defaultContainer.register(value: NotProtocolConformingTestEnum.b) + Grove.defaultContainer.register(NotProtocolConformingTestEnum.b) @Resolve var testEnum: NotProtocolConformingTestEnum XCTAssertEqual(testEnum, .b) @@ -34,7 +34,7 @@ final class GroveValueTypeDependencyTests: XCTestCase { /// Tests registering a value type as a protocol and resolving it as a protocol. func testValueTypeAsProtocolRegistration() { - Grove.defaultContainer.register(as: TestEnumProtocol.self, value: TestEnum.v) + Grove.defaultContainer.register(as: TestEnumProtocol.self, TestEnum.v) @Resolve var testEnum: TestEnumProtocol XCTAssertEqual(testEnum.value, "v")