From 015866aa649d35b75c149c53492bf5d1b1eb13ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Oravec?= Date: Tue, 17 Dec 2024 15:09:19 +0100 Subject: [PATCH 01/25] added async container --- Sources/Container/AsyncContainer.swift | 154 ++++++++++++++++++ Sources/Models/AsyncRegistration.swift | 37 +++++ .../AsyncDependencyRegistering.swift | 94 +++++++++++ .../Resolution/AsyncDependencyResolving.swift | 72 ++++++++ 4 files changed, 357 insertions(+) create mode 100644 Sources/Container/AsyncContainer.swift create mode 100644 Sources/Models/AsyncRegistration.swift create mode 100644 Sources/Protocols/Registration/AsyncDependencyRegistering.swift create mode 100644 Sources/Protocols/Resolution/AsyncDependencyResolving.swift diff --git a/Sources/Container/AsyncContainer.swift b/Sources/Container/AsyncContainer.swift new file mode 100644 index 0000000..6dfaeea --- /dev/null +++ b/Sources/Container/AsyncContainer.swift @@ -0,0 +1,154 @@ +// +// AsyncContainer.swift +// DependencyInjection +// +// Created by Róbert Oravec on 17.12.2024. +// + +import Foundation + +/// Dependency Injection Container where dependencies are registered and from where they are consequently retrieved (i.e. resolved) +public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegistering { + /// Shared singleton + public static let shared: AsyncContainer = { + AsyncContainer() + }() + + private var registrations = [RegistrationIdentifier: AsyncRegistration]() + private var sharedInstances = [RegistrationIdentifier: Any]() + + /// Create new instance of ``Container`` + public init() {} + + /// Remove all registrations and already instantiated shared instances from the container + func clean() { + registrations.removeAll() + + releaseSharedInstances() + } + + /// Remove already instantiated shared instances from the container + func releaseSharedInstances() { + sharedInstances.removeAll() + } + + // MARK: Register dependency, Autoregister dependency + + + /// Register a dependency + /// + /// - Parameters: + /// - type: Type of the dependency to register + /// - scope: Scope of the dependency. If `.new` is used, the `factory` closure is called on each `resolve` call. If `.shared` is used, the `factory` closure is called only the first time, the instance is cached and it is returned for all subsequent `resolve` calls, i.e. it is a singleton + /// - factory: Closure that is called when the dependency is being resolved + public func register( + type: Dependency.Type, + in scope: DependencyScope, + factory: @escaping Factory + ) async { + let registration = AsyncRegistration(type: type, scope: scope, factory: factory) + + registrations[registration.identifier] = registration + + // With a new registration we should clean all shared instances + // because the new registered factory most likely returns different objects and we have no way to tell + sharedInstances[registration.identifier] = nil + } + + // MARK: Register dependency with argument, Autoregister dependency with argument + + /// Register a dependency with an argument + /// + /// The argument is typically a parameter in an initiliazer of the dependency that is not registered in the same container, + /// therefore, it needs to be passed in `resolve` call + /// + /// DISCUSSION: This registration method doesn't have any scope parameter for a reason. + /// The container should always return a new instance for dependencies with arguments as the behaviour for resolving shared instances with arguments is undefined. + /// Should the argument conform to ``Equatable`` to compare the arguments to tell whether a shared instance with a given argument was already resolved? + /// Shared instances are typically not dependent on variable input parameters by definition. + /// If you need to support this usecase, please, keep references to the variable singletons outside of the container. + /// + /// - Parameters: + /// - type: Type of the dependency to register + /// - factory: Closure that is called when the dependency is being resolved + public func register(type: Dependency.Type, factory: @escaping FactoryWithArgument) async { + let registration = AsyncRegistration(type: type, scope: .new, factory: factory) + + registrations[registration.identifier] = registration + } + + // MARK: Resolve dependency + + /// Resolve a dependency that was previously registered with `register` method + /// + /// If a dependency of the given type with the given argument wasn't registered before this method call + /// the method throws ``ResolutionError.dependencyNotRegistered`` + /// + /// - Parameters: + /// - type: Type of the dependency that should be resolved + /// - argument: Argument that will passed as an input parameter to the factory method that was defined with `register` method + public func tryResolve(type: Dependency.Type, argument: Argument) async throws -> Dependency { + let identifier = RegistrationIdentifier(type: type, argument: Argument.self) + + let registration = try getRegistration(with: identifier) + + let dependency: Dependency = try await getDependency(from: registration, with: argument) + + return dependency + } + + /// Resolve a dependency that was previously registered with `register` method + /// + /// If a dependency of the given type wasn't registered before this method call + /// the method throws ``ResolutionError.dependencyNotRegistered`` + /// + /// - Parameters: + /// - type: Type of the dependency that should be resolved + public func tryResolve(type: Dependency.Type) async throws -> Dependency { + let identifier = RegistrationIdentifier(type: type) + + let registration = try getRegistration(with: identifier) + + let dependency: Dependency = try await getDependency(from: registration) + + return dependency + } +} + +// MARK: Private methods +private extension AsyncContainer { + func getRegistration(with identifier: RegistrationIdentifier) throws -> AsyncRegistration { + guard let registration = registrations[identifier] else { + throw ResolutionError.dependencyNotRegistered( + message: "Dependency of type \(identifier.description) wasn't registered in container \(self)" + ) + } + + return registration + } + + func getDependency(from registration: AsyncRegistration, with argument: Any? = nil) async throws -> Dependency { + switch registration.scope { + case .shared: + if let dependency = sharedInstances[registration.identifier] as? Dependency { + return dependency + } + case .new: + break + } + + // We use force cast here because we are sure that the type-casting always succeed + // The reason why the `factory` closure returns ``Any`` is that we have to erase the generic type in order to store the registration + // When the registration is created it can be initialized just with a `factory` that returns the matching type + let dependency = try await registration.factory(self, argument) as! Dependency + + switch registration.scope { + case .shared: + sharedInstances[registration.identifier] = dependency + case .new: + break + } + + return dependency + } +} diff --git a/Sources/Models/AsyncRegistration.swift b/Sources/Models/AsyncRegistration.swift new file mode 100644 index 0000000..c0966b5 --- /dev/null +++ b/Sources/Models/AsyncRegistration.swift @@ -0,0 +1,37 @@ +// +// AsyncRegistration.swift +// DependencyInjection +// +// Created by Róbert Oravec on 16.12.2024. +// + +import Foundation + +/// Object that represents a registered dependency and stores a closure, i.e. a factory that returns the desired dependency +struct AsyncRegistration { + let identifier: RegistrationIdentifier + let scope: DependencyScope + let factory: (any AsyncDependencyResolving, Any?) async throws -> Any + + /// Initializer for registrations that don't need any variable argument + init(type: T.Type, scope: DependencyScope, factory: @escaping (any AsyncDependencyResolving) async -> T) { + self.identifier = RegistrationIdentifier(type: type) + self.scope = scope + self.factory = { resolver, _ in await factory(resolver) } + } + + /// Initializer for registrations that expect a variable argument passed to the factory closure when the dependency is being resolved + init(type: T.Type, scope: DependencyScope, factory: @escaping (any AsyncDependencyResolving, Argument) async -> T) { + let registrationIdentifier = RegistrationIdentifier(type: type, argument: Argument.self) + + self.identifier = registrationIdentifier + self.scope = scope + self.factory = { resolver, arg in + guard let argument = arg as? Argument else { + throw ResolutionError.unmatchingArgumentType(message: "Registration of type \(registrationIdentifier.description) doesn't accept an argument of type \(Argument.self)") + } + + return await factory(resolver, argument) + } + } +} diff --git a/Sources/Protocols/Registration/AsyncDependencyRegistering.swift b/Sources/Protocols/Registration/AsyncDependencyRegistering.swift new file mode 100644 index 0000000..38ac66a --- /dev/null +++ b/Sources/Protocols/Registration/AsyncDependencyRegistering.swift @@ -0,0 +1,94 @@ +// +// AsyncDependencyRegistering.swift +// DependencyInjection +// +// Created by Róbert Oravec on 17.12.2024. +// + +import Foundation + +/// A type that is able to register a dependency +public protocol AsyncDependencyRegistering { + /// Factory closure that instantiates the required dependency + typealias Factory = (any AsyncDependencyResolving) async -> Dependency + + /// Factory closure that instantiates the required dependency with the given variable argument + typealias FactoryWithArgument = (any AsyncDependencyResolving, Argument) async -> Dependency + + /// Register a dependency + /// + /// - Parameters: + /// - type: Type of the dependency to register + /// - scope: Scope of the dependency. If `.new` is used, the `factory` closure is called on each `resolve` call. If `.shared` is used, the `factory` closure is called only the first time, the instance is cached and it is returned for all subsequent `resolve` calls, i.e. it is a singleton + /// - factory: Closure that is called when the dependency is being resolved + func register(type: Dependency.Type, in scope: DependencyScope, factory: @escaping Factory) async + + /// Register a dependency with a variable argument + /// + /// The argument is typically a parameter in an initiliazer of the dependency that is not registered in the same resolver (i.e. container), + /// therefore, it needs to be passed in `resolve` call + /// + /// DISCUSSION: This registration method doesn't have any scope parameter for a reason. + /// The container should always return a new instance for dependencies with arguments as the behaviour for resolving shared instances with arguments is undefined. + /// Should the argument conform to ``Equatable`` to compare the arguments to tell whether a shared instance with a given argument was already resolved? + /// Shared instances are typically not dependent on variable input parameters by definition. + /// If you need to support this usecase, please, keep references to the variable singletons outside of the container. + /// + /// - Parameters: + /// - type: Type of the dependency to register + /// - factory: Closure that is called when the dependency is being resolved + func register(type: Dependency.Type, factory: @escaping FactoryWithArgument) async +} + +// MARK: Overloaded factory methods +public extension AsyncDependencyRegistering { + /// Default ``DependencyScope`` value + /// + /// The default value is `shared` + static var defaultScope: DependencyScope { + DependencyScope.shared + } + + /// Register a dependency in the default ``DependencyScope``, i.e. in the `shared` scope + /// + /// - Parameters: + /// - type: Type of the dependency to register + /// - factory: Closure that is called when the dependency is being resolved + func register(type: Dependency.Type, factory: @escaping Factory) async { + await register(type: type, in: Self.defaultScope, factory: factory) + } + + /// Register a dependency with an implicit type determined by the factory closure return type + /// + /// - Parameters: + /// - scope: Scope of the dependency. If `.new` is used, the `factory` closure is called on each `resolve` call. If `.shared` is used, the `factory` closure is called only the first time, the instance is cached and it is returned for all subsequent `resolve` calls, i.e. it is a singleton + /// - factory: Closure that is called when the dependency is being resolved + func register(in scope: DependencyScope, factory: @escaping Factory) async { + await register(type: Dependency.self, in: scope, factory: factory) + } + + /// Register a dependency with an implicit type determined by the factory closure return type and in the default ``DependencyScope``, i.e. in the `shared` scope + /// + /// - Parameters: + /// - factory: Closure that is called when the dependency is being resolved + func register(factory: @escaping Factory) async { + await register(type: Dependency.self, in: Self.defaultScope, factory: factory) + } + + /// Register a dependency with a variable argument. The type of the dependency is determined implicitly based on the factory closure return type + /// + /// The argument is typically a parameter in an initializer of the dependency that is not registered in the same resolver (i.e. container), + /// therefore, it needs to be passed in `resolve` call + /// + /// DISCUSSION: This registration method doesn't have any scope parameter for a reason. + /// The container should always return a new instance for dependencies with arguments as the behaviour for resolving shared instances with arguments is undefined. + /// Should the argument conform to ``Equatable`` to compare the arguments to tell whether a shared instance with a given argument was already resolved? + /// Shared instances are typically not dependent on variable input parameters by definition. + /// If you need to support this usecase, please, keep references to the variable singletons outside of the container. + /// + /// - Parameters: + /// - factory: Closure that is called when the dependency is being resolved + func register(factory: @escaping FactoryWithArgument) async { + await register(type: Dependency.self, factory: factory) + } +} diff --git a/Sources/Protocols/Resolution/AsyncDependencyResolving.swift b/Sources/Protocols/Resolution/AsyncDependencyResolving.swift new file mode 100644 index 0000000..b4a43fe --- /dev/null +++ b/Sources/Protocols/Resolution/AsyncDependencyResolving.swift @@ -0,0 +1,72 @@ +// +// AsyncDependencyResolving.swift +// DependencyInjection +// +// Created by Róbert Oravec on 17.12.2024. +// + +import Foundation + +/// A type that is able to resolve a dependency +public protocol AsyncDependencyResolving { + /// Resolve a dependency that was previously registered within the container + /// + /// If the container doesn't contain any registration for a dependency with the given type, ``ResolutionError`` is thrown + /// + /// - Parameters: + /// - type: Type of the dependency that should be resolved + func tryResolve(type: T.Type) async throws -> T + + /// Resolve a dependency with a variable argument that was previously registered within the container + /// + /// If the container doesn't contain any registration for a dependency with the given type or if an argument of a different type than expected is passed, ``ResolutionError`` is thrown + /// + /// - Parameters: + /// - type: Type of the dependency that should be resolved + /// - argument: Argument that will be passed as an input parameter to the factory method + func tryResolve(type: T.Type, argument: Argument) async throws -> T +} + +public extension AsyncDependencyResolving { + /// Resolve a dependency that was previously registered within the container + /// + /// If the container doesn't contain any registration for a dependency with the given type, a runtime error occurs + /// + /// - Parameters: + /// - type: Type of the dependency that should be resolved + func resolve(type: T.Type) async -> T { + try! await tryResolve(type: type) + } + + /// Resolve a dependency that was previously registered within the container. A type of the required dependency is inferred from the return type + /// + /// If the container doesn't contain any registration for a dependency with the given type, a runtime error occurs + /// + /// - Parameters: + /// - type: Type of the dependency that should be resolved + func resolve() async -> T { + await resolve(type: T.self) + } + + /// Resolve a dependency with a variable argument that was previously registered within the container + /// + /// If the container doesn't contain any registration for a dependency with the given type or if an argument of a different type than expected is passed, a runtime error occurs + /// + /// - Parameters: + /// - type: Type of the dependency that should be resolved + /// - argument: Argument that will be passed as an input parameter to the factory method + func resolve(type: T.Type, argument: Argument) async -> T { + try! await tryResolve(type: type, argument: argument) + } + + /// Resolve a dependency with a variable argument that was previously registered within the container. The type of the required dependency is inferred from the return type + /// + /// If the container doesn't contain any registration for a dependency with the given type or if an argument of a different type than expected is passed, a runtime error occurs + /// + /// - Parameters: + /// - type: Type of the dependency that should be resolved + /// - argument: Argument that will be passed as an input parameter to the factory method + func resolve(argument: Argument) async -> T { + await resolve(type: T.self, argument: argument) + } +} From 250d052700ae373086a812241885bf2f966ace92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Oravec?= Date: Tue, 17 Dec 2024 15:12:10 +0100 Subject: [PATCH 02/25] added module registration --- Sources/Protocols/ModileRegistration.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 Sources/Protocols/ModileRegistration.swift diff --git a/Sources/Protocols/ModileRegistration.swift b/Sources/Protocols/ModileRegistration.swift new file mode 100644 index 0000000..3ed4b16 --- /dev/null +++ b/Sources/Protocols/ModileRegistration.swift @@ -0,0 +1,10 @@ +// +// ModileRegistration.swift +// DependencyInjection +// +// Created by Róbert Oravec on 17.12.2024. +// + +public protocol ModuleRegistration { + func registerDependencies(in container: AsyncContainer) async +} From f76d19dfc75a877c5d647513a9f990498e6bfac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Oravec?= Date: Tue, 17 Dec 2024 15:36:05 +0100 Subject: [PATCH 03/25] module registration is now static --- Sources/Protocols/ModileRegistration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Protocols/ModileRegistration.swift b/Sources/Protocols/ModileRegistration.swift index 3ed4b16..45b2ef5 100644 --- a/Sources/Protocols/ModileRegistration.swift +++ b/Sources/Protocols/ModileRegistration.swift @@ -6,5 +6,5 @@ // public protocol ModuleRegistration { - func registerDependencies(in container: AsyncContainer) async + static func registerDependencies(in container: AsyncContainer) async } From f53174232b71977bd1516908eb9d5c092b9f3305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Oravec?= Date: Thu, 19 Dec 2024 11:38:12 +0100 Subject: [PATCH 04/25] added tests --- Package.swift | 5 +- Sources/Container/AsyncContainer.swift | 10 +- Sources/Container/Container.swift | 2 +- Sources/Models/AsyncRegistration.swift | 10 +- Sources/Models/DependencyScope.swift | 2 +- .../AsyncDependencyRegistering.swift | 16 +- .../Resolution/AsyncDependencyResolving.swift | 12 +- Tests/Common/AsyncDITestCase.swift | 25 +++ Tests/Common/Dependencies.swift | 16 +- Tests/Container/AsyncArgumentTests.swift | 59 ++++++ Tests/Container/AsyncBaseTests.swift | 86 +++++++++ Tests/Container/AsyncComplexTests.swift | 175 ++++++++++++++++++ 12 files changed, 383 insertions(+), 35 deletions(-) create mode 100644 Tests/Common/AsyncDITestCase.swift create mode 100644 Tests/Container/AsyncArgumentTests.swift create mode 100644 Tests/Container/AsyncBaseTests.swift create mode 100644 Tests/Container/AsyncComplexTests.swift diff --git a/Package.swift b/Package.swift index fb47bfb..ba86923 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.5 +// swift-tools-version:5.8 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -23,7 +23,8 @@ let package = Package( dependencies: [], path: "Sources", swiftSettings: [ - .define("APPLICATION_EXTENSION_API_ONLY") + .define("APPLICATION_EXTENSION_API_ONLY"), + .enableUpcomingFeature("StrictConcurrency") ] ), .testTarget( diff --git a/Sources/Container/AsyncContainer.swift b/Sources/Container/AsyncContainer.swift index 6dfaeea..5a2e384 100644 --- a/Sources/Container/AsyncContainer.swift +++ b/Sources/Container/AsyncContainer.swift @@ -21,14 +21,14 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin public init() {} /// Remove all registrations and already instantiated shared instances from the container - func clean() { + public func clean() { registrations.removeAll() releaseSharedInstances() } /// Remove already instantiated shared instances from the container - func releaseSharedInstances() { + public func releaseSharedInstances() { sharedInstances.removeAll() } @@ -87,7 +87,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin /// - Parameters: /// - type: Type of the dependency that should be resolved /// - argument: Argument that will passed as an input parameter to the factory method that was defined with `register` method - public func tryResolve(type: Dependency.Type, argument: Argument) async throws -> Dependency { + public func tryResolve(type: Dependency.Type, argument: Argument) async throws -> Dependency { let identifier = RegistrationIdentifier(type: type, argument: Argument.self) let registration = try getRegistration(with: identifier) @@ -104,7 +104,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin /// /// - Parameters: /// - type: Type of the dependency that should be resolved - public func tryResolve(type: Dependency.Type) async throws -> Dependency { + public func tryResolve(type: Dependency.Type) async throws -> Dependency { let identifier = RegistrationIdentifier(type: type) let registration = try getRegistration(with: identifier) @@ -127,7 +127,7 @@ private extension AsyncContainer { return registration } - func getDependency(from registration: AsyncRegistration, with argument: Any? = nil) async throws -> Dependency { + func getDependency(from registration: AsyncRegistration, with argument: (any Sendable)? = nil) async throws -> Dependency { switch registration.scope { case .shared: if let dependency = sharedInstances[registration.identifier] as? Dependency { diff --git a/Sources/Container/Container.swift b/Sources/Container/Container.swift index 836a8a1..3903dff 100644 --- a/Sources/Container/Container.swift +++ b/Sources/Container/Container.swift @@ -8,7 +8,7 @@ import Foundation /// Dependency Injection Container where dependencies are registered and from where they are consequently retrieved (i.e. resolved) -open class Container: DependencyWithArgumentAutoregistering, DependencyAutoregistering, DependencyWithArgumentResolving { +open class Container: DependencyWithArgumentAutoregistering, DependencyAutoregistering, DependencyWithArgumentResolving, @unchecked Sendable { /// Shared singleton public static let shared: Container = { Container() diff --git a/Sources/Models/AsyncRegistration.swift b/Sources/Models/AsyncRegistration.swift index c0966b5..266ead7 100644 --- a/Sources/Models/AsyncRegistration.swift +++ b/Sources/Models/AsyncRegistration.swift @@ -7,21 +7,23 @@ import Foundation +typealias AsyncRegistrationFactory = @Sendable (any AsyncDependencyResolving, (any Sendable)?) async throws -> any Sendable + /// Object that represents a registered dependency and stores a closure, i.e. a factory that returns the desired dependency -struct AsyncRegistration { +struct AsyncRegistration: Sendable { let identifier: RegistrationIdentifier let scope: DependencyScope - let factory: (any AsyncDependencyResolving, Any?) async throws -> Any + let factory: AsyncRegistrationFactory /// Initializer for registrations that don't need any variable argument - init(type: T.Type, scope: DependencyScope, factory: @escaping (any AsyncDependencyResolving) async -> T) { + init(type: T.Type, scope: DependencyScope, factory: @Sendable @escaping (any AsyncDependencyResolving) async -> T) { self.identifier = RegistrationIdentifier(type: type) self.scope = scope self.factory = { resolver, _ in await factory(resolver) } } /// Initializer for registrations that expect a variable argument passed to the factory closure when the dependency is being resolved - init(type: T.Type, scope: DependencyScope, factory: @escaping (any AsyncDependencyResolving, Argument) async -> T) { + init(type: T.Type, scope: DependencyScope, factory: @Sendable @escaping (any AsyncDependencyResolving, Argument) async -> T) { let registrationIdentifier = RegistrationIdentifier(type: type, argument: Argument.self) self.identifier = registrationIdentifier diff --git a/Sources/Models/DependencyScope.swift b/Sources/Models/DependencyScope.swift index 81a0146..98238eb 100644 --- a/Sources/Models/DependencyScope.swift +++ b/Sources/Models/DependencyScope.swift @@ -8,7 +8,7 @@ import Foundation /// Scope of a dependency -public enum DependencyScope { +public enum DependencyScope: Sendable { /// A new instance of the dependency is created each time the dependency is resolved from the container. case new diff --git a/Sources/Protocols/Registration/AsyncDependencyRegistering.swift b/Sources/Protocols/Registration/AsyncDependencyRegistering.swift index 38ac66a..fe68a8d 100644 --- a/Sources/Protocols/Registration/AsyncDependencyRegistering.swift +++ b/Sources/Protocols/Registration/AsyncDependencyRegistering.swift @@ -10,10 +10,10 @@ import Foundation /// A type that is able to register a dependency public protocol AsyncDependencyRegistering { /// Factory closure that instantiates the required dependency - typealias Factory = (any AsyncDependencyResolving) async -> Dependency + typealias Factory = @Sendable (any AsyncDependencyResolving) async -> Dependency /// Factory closure that instantiates the required dependency with the given variable argument - typealias FactoryWithArgument = (any AsyncDependencyResolving, Argument) async -> Dependency + typealias FactoryWithArgument = @Sendable (any AsyncDependencyResolving, Argument) async -> Dependency /// Register a dependency /// @@ -21,7 +21,7 @@ public protocol AsyncDependencyRegistering { /// - type: Type of the dependency to register /// - scope: Scope of the dependency. If `.new` is used, the `factory` closure is called on each `resolve` call. If `.shared` is used, the `factory` closure is called only the first time, the instance is cached and it is returned for all subsequent `resolve` calls, i.e. it is a singleton /// - factory: Closure that is called when the dependency is being resolved - func register(type: Dependency.Type, in scope: DependencyScope, factory: @escaping Factory) async + func register(type: Dependency.Type, in scope: DependencyScope, factory: @escaping Factory) async /// Register a dependency with a variable argument /// @@ -37,7 +37,7 @@ public protocol AsyncDependencyRegistering { /// - Parameters: /// - type: Type of the dependency to register /// - factory: Closure that is called when the dependency is being resolved - func register(type: Dependency.Type, factory: @escaping FactoryWithArgument) async + func register(type: Dependency.Type, factory: @escaping FactoryWithArgument) async } // MARK: Overloaded factory methods @@ -54,7 +54,7 @@ public extension AsyncDependencyRegistering { /// - Parameters: /// - type: Type of the dependency to register /// - factory: Closure that is called when the dependency is being resolved - func register(type: Dependency.Type, factory: @escaping Factory) async { + func register(type: Dependency.Type, factory: @escaping Factory) async { await register(type: type, in: Self.defaultScope, factory: factory) } @@ -63,7 +63,7 @@ public extension AsyncDependencyRegistering { /// - Parameters: /// - scope: Scope of the dependency. If `.new` is used, the `factory` closure is called on each `resolve` call. If `.shared` is used, the `factory` closure is called only the first time, the instance is cached and it is returned for all subsequent `resolve` calls, i.e. it is a singleton /// - factory: Closure that is called when the dependency is being resolved - func register(in scope: DependencyScope, factory: @escaping Factory) async { + func register(in scope: DependencyScope, factory: @escaping Factory) async { await register(type: Dependency.self, in: scope, factory: factory) } @@ -71,7 +71,7 @@ public extension AsyncDependencyRegistering { /// /// - Parameters: /// - factory: Closure that is called when the dependency is being resolved - func register(factory: @escaping Factory) async { + func register(factory: @escaping Factory) async { await register(type: Dependency.self, in: Self.defaultScope, factory: factory) } @@ -88,7 +88,7 @@ public extension AsyncDependencyRegistering { /// /// - Parameters: /// - factory: Closure that is called when the dependency is being resolved - func register(factory: @escaping FactoryWithArgument) async { + func register(factory: @escaping FactoryWithArgument) async { await register(type: Dependency.self, factory: factory) } } diff --git a/Sources/Protocols/Resolution/AsyncDependencyResolving.swift b/Sources/Protocols/Resolution/AsyncDependencyResolving.swift index b4a43fe..67fd4f5 100644 --- a/Sources/Protocols/Resolution/AsyncDependencyResolving.swift +++ b/Sources/Protocols/Resolution/AsyncDependencyResolving.swift @@ -15,7 +15,7 @@ public protocol AsyncDependencyResolving { /// /// - Parameters: /// - type: Type of the dependency that should be resolved - func tryResolve(type: T.Type) async throws -> T + func tryResolve(type: T.Type) async throws -> T /// Resolve a dependency with a variable argument that was previously registered within the container /// @@ -24,7 +24,7 @@ public protocol AsyncDependencyResolving { /// - Parameters: /// - type: Type of the dependency that should be resolved /// - argument: Argument that will be passed as an input parameter to the factory method - func tryResolve(type: T.Type, argument: Argument) async throws -> T + func tryResolve(type: T.Type, argument: Argument) async throws -> T } public extension AsyncDependencyResolving { @@ -34,7 +34,7 @@ public extension AsyncDependencyResolving { /// /// - Parameters: /// - type: Type of the dependency that should be resolved - func resolve(type: T.Type) async -> T { + func resolve(type: T.Type) async -> T { try! await tryResolve(type: type) } @@ -44,7 +44,7 @@ public extension AsyncDependencyResolving { /// /// - Parameters: /// - type: Type of the dependency that should be resolved - func resolve() async -> T { + func resolve() async -> T { await resolve(type: T.self) } @@ -55,7 +55,7 @@ public extension AsyncDependencyResolving { /// - Parameters: /// - type: Type of the dependency that should be resolved /// - argument: Argument that will be passed as an input parameter to the factory method - func resolve(type: T.Type, argument: Argument) async -> T { + func resolve(type: T.Type, argument: Argument) async -> T { try! await tryResolve(type: type, argument: argument) } @@ -66,7 +66,7 @@ public extension AsyncDependencyResolving { /// - Parameters: /// - type: Type of the dependency that should be resolved /// - argument: Argument that will be passed as an input parameter to the factory method - func resolve(argument: Argument) async -> T { + func resolve(argument: Argument) async -> T { await resolve(type: T.self, argument: argument) } } diff --git a/Tests/Common/AsyncDITestCase.swift b/Tests/Common/AsyncDITestCase.swift new file mode 100644 index 0000000..9f0b615 --- /dev/null +++ b/Tests/Common/AsyncDITestCase.swift @@ -0,0 +1,25 @@ +// +// AsyncDITestCase.swift +// DependencyInjection +// +// Created by Róbert Oravec on 19.12.2024. +// + +import XCTest +import DependencyInjection + +class AsyncDITestCase: XCTestCase { + var container: AsyncContainer! + + override func setUp() { + super.setUp() + + container = AsyncContainer() + } + + override func tearDown() { + container = nil + + super.tearDown() + } +} diff --git a/Tests/Common/Dependencies.swift b/Tests/Common/Dependencies.swift index 0a6de2b..76127b5 100644 --- a/Tests/Common/Dependencies.swift +++ b/Tests/Common/Dependencies.swift @@ -7,7 +7,7 @@ import Foundation -protocol DIProtocol {} +protocol DIProtocol: Sendable {} struct StructureDependency: Equatable, DIProtocol { static let `default` = StructureDependency(property1: "test") @@ -15,9 +15,9 @@ struct StructureDependency: Equatable, DIProtocol { let property1: String } -class SimpleDependency: DIProtocol {} +final class SimpleDependency: DIProtocol {} -class DependencyWithValueTypeParameter { +final class DependencyWithValueTypeParameter: Sendable { let subDependency: StructureDependency init(subDependency: StructureDependency = .default) { @@ -25,7 +25,7 @@ class DependencyWithValueTypeParameter { } } -class DependencyWithParameter { +final class DependencyWithParameter: Sendable { let subDependency: SimpleDependency init(subDependency: SimpleDependency) { @@ -33,7 +33,7 @@ class DependencyWithParameter { } } -class DependencyWithParameter2 { +final class DependencyWithParameter2: Sendable { let subDependency1: SimpleDependency let subDependency2: DependencyWithValueTypeParameter @@ -43,7 +43,7 @@ class DependencyWithParameter2 { } } -class DependencyWithParameter3 { +final class DependencyWithParameter3: Sendable { let subDependency1: SimpleDependency let subDependency2: DependencyWithValueTypeParameter let subDependency3: DependencyWithParameter @@ -59,7 +59,7 @@ class DependencyWithParameter3 { } } -class DependencyWithParameter4 { +final class DependencyWithParameter4: Sendable { let subDependency1: SimpleDependency let subDependency2: DependencyWithValueTypeParameter let subDependency3: DependencyWithParameter @@ -78,7 +78,7 @@ class DependencyWithParameter4 { } } -class DependencyWithParameter5 { +final class DependencyWithParameter5: Sendable { let subDependency1: SimpleDependency let subDependency2: DependencyWithValueTypeParameter let subDependency3: DependencyWithParameter diff --git a/Tests/Container/AsyncArgumentTests.swift b/Tests/Container/AsyncArgumentTests.swift new file mode 100644 index 0000000..49ac08e --- /dev/null +++ b/Tests/Container/AsyncArgumentTests.swift @@ -0,0 +1,59 @@ +// +// AsyncArgumentTests.swift +// DependencyInjection +// +// Created by Róbert Oravec on 19.12.2024. +// + +import XCTest +import DependencyInjection + +final class AsyncContainerArgumentTests: AsyncDITestCase { + func testRegistration() async { + await container.register { (resolver, argument) -> DependencyWithValueTypeParameter in + DependencyWithValueTypeParameter(subDependency: argument) + } + + let argument = StructureDependency(property1: "48") + let resolvedDependency: DependencyWithValueTypeParameter = await container.resolve(argument: argument) + + XCTAssertEqual(argument, resolvedDependency.subDependency, "Container returned dependency with different argument") + } + + func testRegistrationWithExplicitType() async { + await container.register(type: DependencyWithValueTypeParameter.self) { (resolver, argument) in + DependencyWithValueTypeParameter(subDependency: argument) + } + + let argument = StructureDependency(property1: "48") + let resolvedDependency: DependencyWithValueTypeParameter = await container.resolve(argument: argument) + + XCTAssertEqual(argument, resolvedDependency.subDependency, "Container returned dependency with different argument") + } + + func testUnmatchingArgumentType() async { + await container.register { (resolver, argument) -> DependencyWithValueTypeParameter in + DependencyWithValueTypeParameter(subDependency: argument) + } + + let argument = 48 + + do { + _ = try await container.tryResolve(type: DependencyWithValueTypeParameter.self, argument: argument) + + XCTFail("Expected to throw error") + } catch { + guard let resolutionError = error as? ResolutionError else { + XCTFail("Incorrect error type") + return + } + + switch resolutionError { + case .unmatchingArgumentType: + XCTAssertNotEqual(resolutionError.localizedDescription, "", "Error description is empty") + default: + XCTFail("Incorrect resolution error") + } + } + } +} diff --git a/Tests/Container/AsyncBaseTests.swift b/Tests/Container/AsyncBaseTests.swift new file mode 100644 index 0000000..0507f84 --- /dev/null +++ b/Tests/Container/AsyncBaseTests.swift @@ -0,0 +1,86 @@ +// +// AsyncBaseTests.swift +// DependencyInjection +// +// Created by Róbert Oravec on 19.12.2024. +// + +import XCTest +import DependencyInjection + +final class AsyncBaseTests: AsyncDITestCase { + func testDependencyRegisteredInDefaultScope() async { + await container.register { _ -> SimpleDependency in + SimpleDependency() + } + + let resolvedDependency1: SimpleDependency = await container.resolve() + let resolvedDependency2: SimpleDependency = await container.resolve() + + XCTAssertTrue(resolvedDependency1 === resolvedDependency2, "Container returned different instance") + } + + func testDependencyRegisteredInDefaultScopeWithExplicitType() async { + await container.register(type: SimpleDependency.self) { _ -> SimpleDependency in + SimpleDependency() + } + + let resolvedDependency1: SimpleDependency = await container.resolve() + let resolvedDependency2: SimpleDependency = await container.resolve() + + XCTAssertTrue(resolvedDependency1 === resolvedDependency2, "Container returned different instance") + } + + func testSharedDependency() async { + await container.register(in: .shared) { _ -> SimpleDependency in + SimpleDependency() + } + + let resolvedDependency1: SimpleDependency = await container.resolve() + let resolvedDependency2: SimpleDependency = await container.resolve() + + XCTAssertTrue(resolvedDependency1 === resolvedDependency2, "Container returned different instance") + } + + func testNonSharedDependency() async { + await container.register(in: .new) { _ -> SimpleDependency in + SimpleDependency() + } + + let resolvedDependency1: SimpleDependency = await container.resolve() + let resolvedDependency2: SimpleDependency = await container.resolve() + + XCTAssertTrue(resolvedDependency1 !== resolvedDependency2, "Container returned the same instance") + } + + func testNonSharedDependencyWithExplicitType() async { + await container.register(type: SimpleDependency.self, in: .new) { _ in + SimpleDependency() + } + + let resolvedDependency1: SimpleDependency = await container.resolve() + let resolvedDependency2: SimpleDependency = await container.resolve() + + XCTAssertTrue(resolvedDependency1 !== resolvedDependency2, "Container returned the same instance") + } + + func testUnregisteredDependency() async { + do { + _ = try await container.tryResolve(type: SimpleDependency.self) + + XCTFail("Expected to fail tryResolve") + } catch { + guard let resolutionError = error as? ResolutionError else { + XCTFail("Incorrect error type") + return + } + + switch resolutionError { + case .dependencyNotRegistered: + XCTAssertNotEqual(resolutionError.localizedDescription, "", "Error description is empty") + default: + XCTFail("Incorrect resolution error") + } + } + } +} diff --git a/Tests/Container/AsyncComplexTests.swift b/Tests/Container/AsyncComplexTests.swift new file mode 100644 index 0000000..b2528b2 --- /dev/null +++ b/Tests/Container/AsyncComplexTests.swift @@ -0,0 +1,175 @@ +// +// AsyncComplexTests.swift +// DependencyInjection +// +// Created by Róbert Oravec on 19.12.2024. +// + +import XCTest +import DependencyInjection + +final class AsyncComplexTests: AsyncDITestCase { + func testCleanContainer() async { + await container.register { _ in + SimpleDependency() + } + + let resolvedDependency = try? await container.tryResolve(type: SimpleDependency.self) + + XCTAssertNotNil(resolvedDependency, "Couldn't resolve dependency") + + await container.clean() + + let unresolvedDependency = try? await container.tryResolve(type: SimpleDependency.self) + + XCTAssertNil(unresolvedDependency, "Dependency wasn't cleaned") + } + + func testReleaseSharedInstances() async { + await container.register(in: .shared) { _ in + SimpleDependency() + } + + var resolvedDependency1: SimpleDependency? = await container.resolve(type: SimpleDependency.self) + weak var resolvedDependency2 = await container.resolve(type: SimpleDependency.self) + + XCTAssertNotNil(resolvedDependency1, "Shared instance wasn't resolved") + XCTAssertTrue(resolvedDependency1 === resolvedDependency2, "Different instancies of a shared dependency") + + await container.releaseSharedInstances() + + let resolvedDependency3 = await container.resolve(type: SimpleDependency.self) + + XCTAssertFalse(resolvedDependency1 === resolvedDependency3, "Shared instance wasn't released") + + resolvedDependency1 = nil + + XCTAssertNil(resolvedDependency1, "Shared instance wasn't released") + } + + func testReregistration() async { + await container.register(type: DIProtocol.self, in: .shared) { _ in + SimpleDependency() + } + + let resolvedSimpleDependency = await container.resolve(type: DIProtocol.self) + + XCTAssertTrue(resolvedSimpleDependency is SimpleDependency, "Resolved dependency of wrong type") + + await container.register(type: DIProtocol.self, in: .shared) { _ in + StructureDependency.default + } + + let resolvedStructureDependency = await container.resolve(type: DIProtocol.self) + + XCTAssertTrue(resolvedStructureDependency is StructureDependency, "Resolved dependency of wrong type") + } + + func testSameDependencyTypeRegisteredWithDifferentTypes() async { + await container.register(type: DIProtocol.self, in: .shared) { _ in + StructureDependency(property1: "first") + } + + await container.register(type: StructureDependency.self, in: .shared) { _ in + StructureDependency(property1: "second") + } + + let resolvedProtocolDependency: DIProtocol = await container.resolve() + let resolvedTypeDependency: StructureDependency = await container.resolve() + + XCTAssertTrue(resolvedProtocolDependency is StructureDependency, "Resolved dependency of wrong type") + XCTAssertEqual(resolvedTypeDependency.property1, "second", "Resolved dependency from a wrong factory") + + XCTAssertNotEqual( + (resolvedProtocolDependency as? StructureDependency)?.property1, + resolvedTypeDependency.property1, + "Resolved same instances" + ) + } + + func testCombiningSharedAndNonsharedDependencies() async { + await container.register(in: .new) { _ in + SimpleDependency() + } + await container.register(in: .shared) { + DependencyWithParameter(subDependency: await $0.resolve()) + } + await container.register { + DependencyWithParameter3( + subDependency1: await $0.resolve(), + subDependency2: $1, + subDependency3: await $0.resolve() + ) + } + + let argumentDependency1 = DependencyWithValueTypeParameter( + subDependency: StructureDependency(property1: "first") + ) + let argumentDependency2 = DependencyWithValueTypeParameter( + subDependency: StructureDependency(property1: "second") + ) + + let resolvedDependency1: DependencyWithParameter = await container.resolve() + let resolvedDependency2: DependencyWithParameter = await container.resolve() + let resolvedDependency3 = await container.resolve(type: DependencyWithParameter3.self, argument: argumentDependency1) + let resolvedDependency4 = await container.resolve(type: DependencyWithParameter3.self, argument: argumentDependency2) + + XCTAssertTrue(resolvedDependency1 === resolvedDependency2, "Resolved different instances") + XCTAssertTrue(resolvedDependency1.subDependency === resolvedDependency2.subDependency, "Resolved different instances") + + XCTAssertFalse(resolvedDependency1.subDependency === resolvedDependency3.subDependency1, "Resolved the same instance for a subdependency") + XCTAssertFalse(resolvedDependency3.subDependency1 === resolvedDependency4.subDependency1, "Resolved the same instance for a subdependency") + + XCTAssertFalse(resolvedDependency3 === resolvedDependency4, "Resolved same instances") + + XCTAssertNotEqual( + resolvedDependency3.subDependency2.subDependency.property1, + resolvedDependency4.subDependency2.subDependency.property1, + "Resolved instances with the same argument" + ) + } + + func testCombiningSharedAndNonsharedDependenciesWithExplicitFactories() async { + await container.register(in: .new) { _ in + SimpleDependency() + } + await container.register(in: .shared) { + DependencyWithParameter( + subDependency: await $0.resolve() + ) + } + await container.register { resolver, argument in + DependencyWithParameter3( + subDependency1: await resolver.resolve(), + subDependency2: argument, + subDependency3: await resolver.resolve() + ) + } + + let argumentDependency1 = DependencyWithValueTypeParameter( + subDependency: StructureDependency(property1: "first") + ) + let argumentDependency2 = DependencyWithValueTypeParameter( + subDependency: StructureDependency(property1: "second") + ) + + let resolvedDependency1: DependencyWithParameter = await container.resolve() + let resolvedDependency2: DependencyWithParameter = await container.resolve() + let resolvedDependency3 = await container.resolve(type: DependencyWithParameter3.self, argument: argumentDependency1) + let resolvedDependency4 = await container.resolve(type: DependencyWithParameter3.self, argument: argumentDependency2) + + XCTAssertTrue(resolvedDependency1 === resolvedDependency2, "Resolved different instances") + XCTAssertTrue(resolvedDependency1.subDependency === resolvedDependency2.subDependency, "Resolved different instances") + + XCTAssertFalse(resolvedDependency1.subDependency === resolvedDependency3.subDependency1, "Resolved the same instance for a subdependency") + XCTAssertFalse(resolvedDependency3.subDependency1 === resolvedDependency4.subDependency1, "Resolved the same instance for a subdependency") + + XCTAssertFalse(resolvedDependency3 === resolvedDependency4, "Resolved same instances") + + XCTAssertNotEqual( + resolvedDependency3.subDependency2.subDependency.property1, + resolvedDependency4.subDependency2.subDependency.property1, + "Resolved instances with the same argument" + ) + } +} From 481bda2e0615a229beb72068cb56fb7980cde2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Oravec?= Date: Thu, 19 Dec 2024 12:47:47 +0100 Subject: [PATCH 05/25] updated registration protocols --- ...gistration.swift => AsyncModuleRegistration.swift} | 3 ++- Sources/Protocols/ModuleRegistration.swift | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) rename Sources/Protocols/{ModileRegistration.swift => AsyncModuleRegistration.swift} (61%) create mode 100644 Sources/Protocols/ModuleRegistration.swift diff --git a/Sources/Protocols/ModileRegistration.swift b/Sources/Protocols/AsyncModuleRegistration.swift similarity index 61% rename from Sources/Protocols/ModileRegistration.swift rename to Sources/Protocols/AsyncModuleRegistration.swift index 45b2ef5..4b2212f 100644 --- a/Sources/Protocols/ModileRegistration.swift +++ b/Sources/Protocols/AsyncModuleRegistration.swift @@ -5,6 +5,7 @@ // Created by Róbert Oravec on 17.12.2024. // -public protocol ModuleRegistration { +/// Protocol used to enforce common naming of registration in a module. +public protocol AsyncModuleRegistration { static func registerDependencies(in container: AsyncContainer) async } diff --git a/Sources/Protocols/ModuleRegistration.swift b/Sources/Protocols/ModuleRegistration.swift new file mode 100644 index 0000000..e7a182b --- /dev/null +++ b/Sources/Protocols/ModuleRegistration.swift @@ -0,0 +1,11 @@ +// +// ModuleRegistration.swift +// DependencyInjection +// +// Created by Róbert Oravec on 19.12.2024. +// + +/// Protocol used to enforce common naming of registration in a module. +public protocol ModuleRegistration { + static func registerDependencies(in container: Container) +} From 6690de5064960795d0b948ab8c7aca328a65d1b6 Mon Sep 17 00:00:00 2001 From: Tomas Cejka Date: Thu, 19 Dec 2024 17:58:00 +0100 Subject: [PATCH 06/25] [chore] fix comment typo --- Sources/Protocols/AsyncModuleRegistration.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Protocols/AsyncModuleRegistration.swift b/Sources/Protocols/AsyncModuleRegistration.swift index 4b2212f..0abe881 100644 --- a/Sources/Protocols/AsyncModuleRegistration.swift +++ b/Sources/Protocols/AsyncModuleRegistration.swift @@ -1,5 +1,5 @@ // -// ModileRegistration.swift +// AsyncModuleRegistration.swift // DependencyInjection // // Created by Róbert Oravec on 17.12.2024. From edd8abb7350dd025004c345eb8054fd60f61abaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Oravec?= Date: Tue, 7 Jan 2025 08:41:52 +0100 Subject: [PATCH 07/25] increased swift tools version --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index ba86923..c3beb2c 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.8 +// swift-tools-version:5.9 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription From 8726479204ecfd42865d973bb14633a39ccd79c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=B3bert=20Oravec?= Date: Tue, 7 Jan 2025 08:46:20 +0100 Subject: [PATCH 08/25] changed factory in async registration to async registration factory --- Sources/Container/AsyncContainer.swift | 2 +- Sources/Models/AsyncRegistration.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/Container/AsyncContainer.swift b/Sources/Container/AsyncContainer.swift index 5a2e384..c212208 100644 --- a/Sources/Container/AsyncContainer.swift +++ b/Sources/Container/AsyncContainer.swift @@ -140,7 +140,7 @@ private extension AsyncContainer { // We use force cast here because we are sure that the type-casting always succeed // The reason why the `factory` closure returns ``Any`` is that we have to erase the generic type in order to store the registration // When the registration is created it can be initialized just with a `factory` that returns the matching type - let dependency = try await registration.factory(self, argument) as! Dependency + let dependency = try await registration.asyncRegistrationFactory(self, argument) as! Dependency switch registration.scope { case .shared: diff --git a/Sources/Models/AsyncRegistration.swift b/Sources/Models/AsyncRegistration.swift index 266ead7..bbfd543 100644 --- a/Sources/Models/AsyncRegistration.swift +++ b/Sources/Models/AsyncRegistration.swift @@ -13,13 +13,13 @@ typealias AsyncRegistrationFactory = @Sendable (any AsyncDependencyResolving, (a struct AsyncRegistration: Sendable { let identifier: RegistrationIdentifier let scope: DependencyScope - let factory: AsyncRegistrationFactory + let asyncRegistrationFactory: AsyncRegistrationFactory /// Initializer for registrations that don't need any variable argument init(type: T.Type, scope: DependencyScope, factory: @Sendable @escaping (any AsyncDependencyResolving) async -> T) { self.identifier = RegistrationIdentifier(type: type) self.scope = scope - self.factory = { resolver, _ in await factory(resolver) } + self.asyncRegistrationFactory = { resolver, _ in await factory(resolver) } } /// Initializer for registrations that expect a variable argument passed to the factory closure when the dependency is being resolved @@ -28,7 +28,7 @@ struct AsyncRegistration: Sendable { self.identifier = registrationIdentifier self.scope = scope - self.factory = { resolver, arg in + self.asyncRegistrationFactory = { resolver, arg in guard let argument = arg as? Argument else { throw ResolutionError.unmatchingArgumentType(message: "Registration of type \(registrationIdentifier.description) doesn't accept an argument of type \(Argument.self)") } From 36a4e9e2918c4e319b20df7898a753b6a91b8599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Thu, 6 Feb 2025 10:13:27 +0100 Subject: [PATCH 09/25] Update Sources/Container/AsyncContainer.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Filip Haškovec --- Sources/Container/AsyncContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Container/AsyncContainer.swift b/Sources/Container/AsyncContainer.swift index c212208..3467438 100644 --- a/Sources/Container/AsyncContainer.swift +++ b/Sources/Container/AsyncContainer.swift @@ -17,7 +17,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin private var registrations = [RegistrationIdentifier: AsyncRegistration]() private var sharedInstances = [RegistrationIdentifier: Any]() - /// Create new instance of ``Container`` + /// Create new instance of ``AsyncContainer`` public init() {} /// Remove all registrations and already instantiated shared instances from the container From e53137792397a4d17e4811b178181de776a6c94f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Thu, 6 Feb 2025 10:13:52 +0100 Subject: [PATCH 10/25] Update Sources/Container/AsyncContainer.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Filip Haškovec --- Sources/Container/AsyncContainer.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/Container/AsyncContainer.swift b/Sources/Container/AsyncContainer.swift index 3467438..baa79b1 100644 --- a/Sources/Container/AsyncContainer.swift +++ b/Sources/Container/AsyncContainer.swift @@ -34,7 +34,6 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin // MARK: Register dependency, Autoregister dependency - /// Register a dependency /// /// - Parameters: From e4550be49f83949822c492ac299cc9315a836e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Thu, 6 Feb 2025 10:22:11 +0100 Subject: [PATCH 11/25] feat: minor fixes --- Sources/Container/AsyncContainer.swift | 1 - Sources/Protocols/Resolution/AsyncDependencyResolving.swift | 3 --- 2 files changed, 4 deletions(-) diff --git a/Sources/Container/AsyncContainer.swift b/Sources/Container/AsyncContainer.swift index c212208..df2b706 100644 --- a/Sources/Container/AsyncContainer.swift +++ b/Sources/Container/AsyncContainer.swift @@ -34,7 +34,6 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin // MARK: Register dependency, Autoregister dependency - /// Register a dependency /// /// - Parameters: diff --git a/Sources/Protocols/Resolution/AsyncDependencyResolving.swift b/Sources/Protocols/Resolution/AsyncDependencyResolving.swift index 67fd4f5..afd4208 100644 --- a/Sources/Protocols/Resolution/AsyncDependencyResolving.swift +++ b/Sources/Protocols/Resolution/AsyncDependencyResolving.swift @@ -42,8 +42,6 @@ public extension AsyncDependencyResolving { /// /// If the container doesn't contain any registration for a dependency with the given type, a runtime error occurs /// - /// - Parameters: - /// - type: Type of the dependency that should be resolved func resolve() async -> T { await resolve(type: T.self) } @@ -64,7 +62,6 @@ public extension AsyncDependencyResolving { /// If the container doesn't contain any registration for a dependency with the given type or if an argument of a different type than expected is passed, a runtime error occurs /// /// - Parameters: - /// - type: Type of the dependency that should be resolved /// - argument: Argument that will be passed as an input parameter to the factory method func resolve(argument: Argument) async -> T { await resolve(type: T.self, argument: argument) From 66d1149f3bfd15a17d879ad0b93ec6c7f5cad707 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Thu, 6 Feb 2025 10:32:24 +0100 Subject: [PATCH 12/25] feat: sync/async folders --- Sources/Container/{ => Async}/AsyncContainer.swift | 0 Sources/Container/{ => Sync}/Container.swift | 0 Sources/Models/{ => Async}/AsyncRegistration.swift | 0 Sources/Models/{ => Sync}/Registration.swift | 0 .../Registration/{ => Async}/AsyncDependencyRegistering.swift | 0 .../Registration/{ => Sync}/DependencyAutoregistering.swift | 0 .../Protocols/Registration/{ => Sync}/DependencyRegistering.swift | 0 .../{ => Sync}/DependencyWithArgumentAutoregistering.swift | 0 .../{ => Sync}/DependencyWithArgumentRegistering.swift | 0 .../Resolution/{ => Async}/AsyncDependencyResolving.swift | 0 .../{ => Resolution/Async}/AsyncModuleRegistration.swift | 0 Sources/Protocols/Resolution/{ => Sync}/DependencyResolving.swift | 0 .../Resolution/{ => Sync}/DependencyWithArgumentResolving.swift | 0 Sources/Protocols/{ => Resolution/Sync}/ModuleRegistration.swift | 0 Tests/Common/{ => Async}/AsyncDITestCase.swift | 0 Tests/Common/{ => Sync}/DITestCase.swift | 0 Tests/Container/{ => Async}/AsyncArgumentTests.swift | 0 Tests/Container/{ => Async}/AsyncBaseTests.swift | 0 Tests/Container/{ => Async}/AsyncComplexTests.swift | 0 Tests/Container/{ => Sync}/ArgumentTests.swift | 0 Tests/Container/{ => Sync}/AutoregistrationTests.swift | 0 Tests/Container/{ => Sync}/AutoregistrationWithArgumentTest.swift | 0 Tests/Container/{ => Sync}/BaseTests.swift | 0 Tests/Container/{ => Sync}/ComplexTests.swift | 0 24 files changed, 0 insertions(+), 0 deletions(-) rename Sources/Container/{ => Async}/AsyncContainer.swift (100%) rename Sources/Container/{ => Sync}/Container.swift (100%) rename Sources/Models/{ => Async}/AsyncRegistration.swift (100%) rename Sources/Models/{ => Sync}/Registration.swift (100%) rename Sources/Protocols/Registration/{ => Async}/AsyncDependencyRegistering.swift (100%) rename Sources/Protocols/Registration/{ => Sync}/DependencyAutoregistering.swift (100%) rename Sources/Protocols/Registration/{ => Sync}/DependencyRegistering.swift (100%) rename Sources/Protocols/Registration/{ => Sync}/DependencyWithArgumentAutoregistering.swift (100%) rename Sources/Protocols/Registration/{ => Sync}/DependencyWithArgumentRegistering.swift (100%) rename Sources/Protocols/Resolution/{ => Async}/AsyncDependencyResolving.swift (100%) rename Sources/Protocols/{ => Resolution/Async}/AsyncModuleRegistration.swift (100%) rename Sources/Protocols/Resolution/{ => Sync}/DependencyResolving.swift (100%) rename Sources/Protocols/Resolution/{ => Sync}/DependencyWithArgumentResolving.swift (100%) rename Sources/Protocols/{ => Resolution/Sync}/ModuleRegistration.swift (100%) rename Tests/Common/{ => Async}/AsyncDITestCase.swift (100%) rename Tests/Common/{ => Sync}/DITestCase.swift (100%) rename Tests/Container/{ => Async}/AsyncArgumentTests.swift (100%) rename Tests/Container/{ => Async}/AsyncBaseTests.swift (100%) rename Tests/Container/{ => Async}/AsyncComplexTests.swift (100%) rename Tests/Container/{ => Sync}/ArgumentTests.swift (100%) rename Tests/Container/{ => Sync}/AutoregistrationTests.swift (100%) rename Tests/Container/{ => Sync}/AutoregistrationWithArgumentTest.swift (100%) rename Tests/Container/{ => Sync}/BaseTests.swift (100%) rename Tests/Container/{ => Sync}/ComplexTests.swift (100%) diff --git a/Sources/Container/AsyncContainer.swift b/Sources/Container/Async/AsyncContainer.swift similarity index 100% rename from Sources/Container/AsyncContainer.swift rename to Sources/Container/Async/AsyncContainer.swift diff --git a/Sources/Container/Container.swift b/Sources/Container/Sync/Container.swift similarity index 100% rename from Sources/Container/Container.swift rename to Sources/Container/Sync/Container.swift diff --git a/Sources/Models/AsyncRegistration.swift b/Sources/Models/Async/AsyncRegistration.swift similarity index 100% rename from Sources/Models/AsyncRegistration.swift rename to Sources/Models/Async/AsyncRegistration.swift diff --git a/Sources/Models/Registration.swift b/Sources/Models/Sync/Registration.swift similarity index 100% rename from Sources/Models/Registration.swift rename to Sources/Models/Sync/Registration.swift diff --git a/Sources/Protocols/Registration/AsyncDependencyRegistering.swift b/Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift similarity index 100% rename from Sources/Protocols/Registration/AsyncDependencyRegistering.swift rename to Sources/Protocols/Registration/Async/AsyncDependencyRegistering.swift diff --git a/Sources/Protocols/Registration/DependencyAutoregistering.swift b/Sources/Protocols/Registration/Sync/DependencyAutoregistering.swift similarity index 100% rename from Sources/Protocols/Registration/DependencyAutoregistering.swift rename to Sources/Protocols/Registration/Sync/DependencyAutoregistering.swift diff --git a/Sources/Protocols/Registration/DependencyRegistering.swift b/Sources/Protocols/Registration/Sync/DependencyRegistering.swift similarity index 100% rename from Sources/Protocols/Registration/DependencyRegistering.swift rename to Sources/Protocols/Registration/Sync/DependencyRegistering.swift diff --git a/Sources/Protocols/Registration/DependencyWithArgumentAutoregistering.swift b/Sources/Protocols/Registration/Sync/DependencyWithArgumentAutoregistering.swift similarity index 100% rename from Sources/Protocols/Registration/DependencyWithArgumentAutoregistering.swift rename to Sources/Protocols/Registration/Sync/DependencyWithArgumentAutoregistering.swift diff --git a/Sources/Protocols/Registration/DependencyWithArgumentRegistering.swift b/Sources/Protocols/Registration/Sync/DependencyWithArgumentRegistering.swift similarity index 100% rename from Sources/Protocols/Registration/DependencyWithArgumentRegistering.swift rename to Sources/Protocols/Registration/Sync/DependencyWithArgumentRegistering.swift diff --git a/Sources/Protocols/Resolution/AsyncDependencyResolving.swift b/Sources/Protocols/Resolution/Async/AsyncDependencyResolving.swift similarity index 100% rename from Sources/Protocols/Resolution/AsyncDependencyResolving.swift rename to Sources/Protocols/Resolution/Async/AsyncDependencyResolving.swift diff --git a/Sources/Protocols/AsyncModuleRegistration.swift b/Sources/Protocols/Resolution/Async/AsyncModuleRegistration.swift similarity index 100% rename from Sources/Protocols/AsyncModuleRegistration.swift rename to Sources/Protocols/Resolution/Async/AsyncModuleRegistration.swift diff --git a/Sources/Protocols/Resolution/DependencyResolving.swift b/Sources/Protocols/Resolution/Sync/DependencyResolving.swift similarity index 100% rename from Sources/Protocols/Resolution/DependencyResolving.swift rename to Sources/Protocols/Resolution/Sync/DependencyResolving.swift diff --git a/Sources/Protocols/Resolution/DependencyWithArgumentResolving.swift b/Sources/Protocols/Resolution/Sync/DependencyWithArgumentResolving.swift similarity index 100% rename from Sources/Protocols/Resolution/DependencyWithArgumentResolving.swift rename to Sources/Protocols/Resolution/Sync/DependencyWithArgumentResolving.swift diff --git a/Sources/Protocols/ModuleRegistration.swift b/Sources/Protocols/Resolution/Sync/ModuleRegistration.swift similarity index 100% rename from Sources/Protocols/ModuleRegistration.swift rename to Sources/Protocols/Resolution/Sync/ModuleRegistration.swift diff --git a/Tests/Common/AsyncDITestCase.swift b/Tests/Common/Async/AsyncDITestCase.swift similarity index 100% rename from Tests/Common/AsyncDITestCase.swift rename to Tests/Common/Async/AsyncDITestCase.swift diff --git a/Tests/Common/DITestCase.swift b/Tests/Common/Sync/DITestCase.swift similarity index 100% rename from Tests/Common/DITestCase.swift rename to Tests/Common/Sync/DITestCase.swift diff --git a/Tests/Container/AsyncArgumentTests.swift b/Tests/Container/Async/AsyncArgumentTests.swift similarity index 100% rename from Tests/Container/AsyncArgumentTests.swift rename to Tests/Container/Async/AsyncArgumentTests.swift diff --git a/Tests/Container/AsyncBaseTests.swift b/Tests/Container/Async/AsyncBaseTests.swift similarity index 100% rename from Tests/Container/AsyncBaseTests.swift rename to Tests/Container/Async/AsyncBaseTests.swift diff --git a/Tests/Container/AsyncComplexTests.swift b/Tests/Container/Async/AsyncComplexTests.swift similarity index 100% rename from Tests/Container/AsyncComplexTests.swift rename to Tests/Container/Async/AsyncComplexTests.swift diff --git a/Tests/Container/ArgumentTests.swift b/Tests/Container/Sync/ArgumentTests.swift similarity index 100% rename from Tests/Container/ArgumentTests.swift rename to Tests/Container/Sync/ArgumentTests.swift diff --git a/Tests/Container/AutoregistrationTests.swift b/Tests/Container/Sync/AutoregistrationTests.swift similarity index 100% rename from Tests/Container/AutoregistrationTests.swift rename to Tests/Container/Sync/AutoregistrationTests.swift diff --git a/Tests/Container/AutoregistrationWithArgumentTest.swift b/Tests/Container/Sync/AutoregistrationWithArgumentTest.swift similarity index 100% rename from Tests/Container/AutoregistrationWithArgumentTest.swift rename to Tests/Container/Sync/AutoregistrationWithArgumentTest.swift diff --git a/Tests/Container/BaseTests.swift b/Tests/Container/Sync/BaseTests.swift similarity index 100% rename from Tests/Container/BaseTests.swift rename to Tests/Container/Sync/BaseTests.swift diff --git a/Tests/Container/ComplexTests.swift b/Tests/Container/Sync/ComplexTests.swift similarity index 100% rename from Tests/Container/ComplexTests.swift rename to Tests/Container/Sync/ComplexTests.swift From 984d2238566b83dfff0abb508c8aa3cd0598addf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Thu, 6 Feb 2025 11:20:12 +0100 Subject: [PATCH 13/25] feat: async init test --- Tests/Common/Dependencies.swift | 8 ++++++++ Tests/Container/Async/AsyncArgumentTests.swift | 11 +++++++++++ 2 files changed, 19 insertions(+) diff --git a/Tests/Common/Dependencies.swift b/Tests/Common/Dependencies.swift index 76127b5..aee30fa 100644 --- a/Tests/Common/Dependencies.swift +++ b/Tests/Common/Dependencies.swift @@ -99,3 +99,11 @@ final class DependencyWithParameter5: Sendable { self.subDependency5 = subDependency5 } } + +final class DependencyWithAsyncInitWithParameter: Sendable { + let subDependency: StructureDependency + + init(subDependency: StructureDependency = .default) async { + self.subDependency = subDependency + } +} diff --git a/Tests/Container/Async/AsyncArgumentTests.swift b/Tests/Container/Async/AsyncArgumentTests.swift index 49ac08e..64f20c9 100644 --- a/Tests/Container/Async/AsyncArgumentTests.swift +++ b/Tests/Container/Async/AsyncArgumentTests.swift @@ -56,4 +56,15 @@ final class AsyncContainerArgumentTests: AsyncDITestCase { } } } + + func testRegistrationWithAsyncInit() async { + await container.register { (resolver, argument) -> DependencyWithAsyncInitWithParameter in + await DependencyWithAsyncInitWithParameter(subDependency: argument) + } + + let argument = StructureDependency(property1: "48") + let resolvedDependency: DependencyWithAsyncInitWithParameter = await container.resolve(argument: argument) + + XCTAssertEqual(argument, resolvedDependency.subDependency, "Container returned dependency with different argument") + } } From 5362b87a68c40c217b66e1770051b19d175030c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Mon, 10 Feb 2025 15:01:47 +0100 Subject: [PATCH 14/25] feat: bundler version --- .bundler-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .bundler-version diff --git a/.bundler-version b/.bundler-version new file mode 100644 index 0000000..19244e8 --- /dev/null +++ b/.bundler-version @@ -0,0 +1 @@ +2.4.21 \ No newline at end of file From 5327c7390dd6b4a565e7b6c552b3a2837498d422 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Mon, 10 Feb 2025 15:19:42 +0100 Subject: [PATCH 15/25] feat: bundler fix --- Gemfile | 2 +- Gemfile.lock | 210 ++++++++++++++++++++++++++++----------------------- 2 files changed, 116 insertions(+), 96 deletions(-) diff --git a/Gemfile b/Gemfile index 6630cbd..6ff18ff 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ # STRV source 'https://rubygems.org' -gem 'bundler', '~> 2.1.0' +gem 'bundler', '~> 2.4.20' gem 'danger', '~> 8.2.0' gem 'fastlane', '~> 2.178.0' diff --git a/Gemfile.lock b/Gemfile.lock index 95e3427..a7e26a2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,29 +1,34 @@ GEM remote: https://rubygems.org/ specs: - CFPropertyList (3.0.3) - addressable (2.7.0) - public_suffix (>= 2.0.2, < 5.0) - artifactory (3.0.15) + CFPropertyList (3.0.7) + base64 + nkf + rexml + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + artifactory (3.0.17) atomos (0.1.3) - aws-eventstream (1.1.1) - aws-partitions (1.434.0) - aws-sdk-core (3.113.0) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-kms (1.43.0) - aws-sdk-core (~> 3, >= 3.112.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.92.0) - aws-sdk-core (~> 3, >= 3.112.0) + aws-eventstream (1.3.0) + aws-partitions (1.1048.0) + aws-sdk-core (3.218.1) + aws-eventstream (~> 1, >= 1.3.0) + aws-partitions (~> 1, >= 1.992.0) + aws-sigv4 (~> 1.9) + base64 + jmespath (~> 1, >= 1.6.1) + aws-sdk-kms (1.98.0) + aws-sdk-core (~> 3, >= 3.216.0) + aws-sigv4 (~> 1.5) + aws-sdk-s3 (1.180.0) + aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.2.3) + aws-sigv4 (~> 1.5) + aws-sigv4 (1.11.0) aws-eventstream (~> 1, >= 1.0.2) babosa (1.0.4) - claide (1.0.3) + base64 (0.2.0) + claide (1.1.0) claide-plugins (0.9.2) cork nap @@ -48,27 +53,43 @@ GEM octokit (~> 4.7) terminal-table (>= 1, < 4) declarative (0.0.20) - declarative-option (0.1.0) - digest-crc (0.6.3) + digest-crc (0.7.0) rake (>= 12.0.0, < 14.0.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.6) - emoji_regex (3.2.2) - excon (0.79.0) - faraday (1.3.0) + domain_name (0.6.20240107) + dotenv (2.8.1) + emoji_regex (3.2.3) + excon (0.112.0) + faraday (1.10.4) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) faraday-net_http (~> 1.0) - multipart-post (>= 1.2, < 3) - ruby2_keywords + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) faraday-cookie_jar (0.0.7) faraday (>= 0.8.0) http-cookie (~> 1.0.0) - faraday-http-cache (2.2.0) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-http-cache (2.5.1) faraday (>= 0.8) - faraday-net_http (1.0.1) - faraday_middleware (1.0.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.1.0) + multipart-post (~> 2.0) + faraday-net_http (1.0.2) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + faraday_middleware (1.2.1) faraday (~> 1.0) - fastimage (2.2.3) + fastimage (2.4.0) fastlane (2.178.0) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) @@ -108,7 +129,8 @@ GEM xcpretty (~> 0.3.0) xcpretty-travis-formatter (>= 0.0.3) gh_inspector (1.1.3) - git (1.8.1) + git (1.19.1) + addressable (~> 2.8) rchardet (~> 1.8) google-api-client (0.38.0) addressable (~> 2.5, >= 2.5.1) @@ -118,124 +140,122 @@ GEM representable (~> 3.0) retriable (>= 2.0, < 4.0) signet (~> 0.12) - google-apis-core (0.3.0) + google-apis-core (0.11.3) addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.14) - httpclient (>= 2.8.1, < 3.0) + googleauth (>= 0.16.2, < 2.a) + httpclient (>= 2.8.1, < 3.a) mini_mime (~> 1.0) representable (~> 3.0) - retriable (>= 2.0, < 4.0) + retriable (>= 2.0, < 4.a) rexml - signet (~> 0.14) - webrick - google-apis-iamcredentials_v1 (0.2.0) - google-apis-core (~> 0.1) - google-apis-storage_v1 (0.3.0) - google-apis-core (~> 0.1) - google-cloud-core (1.6.0) - google-cloud-env (~> 1.0) + google-apis-iamcredentials_v1 (0.17.0) + google-apis-core (>= 0.11.0, < 2.a) + google-apis-storage_v1 (0.31.0) + google-apis-core (>= 0.11.0, < 2.a) + google-cloud-core (1.7.1) + google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (1.5.0) - faraday (>= 0.17.3, < 2.0) - google-cloud-errors (1.1.0) - google-cloud-storage (1.31.0) - addressable (~> 2.5) + google-cloud-env (2.1.1) + faraday (>= 1.0, < 3.a) + google-cloud-errors (1.4.0) + google-cloud-storage (1.47.0) + addressable (~> 2.8) digest-crc (~> 0.4) google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.1) - google-cloud-core (~> 1.2) - googleauth (~> 0.9) + google-apis-storage_v1 (~> 0.31.0) + google-cloud-core (~> 1.6) + googleauth (>= 0.16.2, < 2.a) mini_mime (~> 1.0) - googleauth (0.16.0) + googleauth (0.17.1) faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.14) + signet (~> 0.15) highline (1.7.10) - http-cookie (1.0.3) + http-cookie (1.0.8) domain_name (~> 0.5) httpclient (2.8.3) - jmespath (1.4.0) - json (2.5.1) - jwt (2.2.2) - kramdown (2.3.1) - rexml + jmespath (1.6.2) + json (2.10.0) + jwt (2.10.1) + base64 + kramdown (2.5.1) + rexml (>= 3.3.9) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) memoist (0.16.2) - mini_magick (4.11.0) - mini_mime (1.0.2) + mini_magick (4.13.2) + mini_mime (1.1.5) multi_json (1.15.0) multipart-post (2.0.0) - nanaimo (0.3.0) + nanaimo (0.4.0) nap (1.1.0) naturally (2.2.1) + nkf (0.2.0) no_proxy_fix (0.1.2) - octokit (4.20.0) - faraday (>= 0.9) - sawyer (~> 0.8.0, >= 0.5.3) + octokit (4.25.1) + faraday (>= 1, < 3) + sawyer (~> 0.9) open4 (1.3.4) - os (1.1.1) - plist (3.6.0) - public_suffix (4.0.6) - rake (13.0.3) + os (1.1.4) + plist (3.7.2) + public_suffix (5.1.1) + rake (13.2.1) rchardet (1.8.0) - representable (3.0.4) + representable (3.2.0) declarative (< 0.1.0) - declarative-option (< 0.2.0) + trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.4) + rexml (3.4.0) rouge (2.0.7) - ruby2_keywords (0.0.4) - rubyzip (2.3.0) - sawyer (0.8.2) + ruby2_keywords (0.0.5) + rubyzip (2.4.1) + sawyer (0.9.2) addressable (>= 2.3.5) - faraday (> 0.8, < 2.0) + faraday (>= 0.17.3, < 3) security (0.1.3) - signet (0.15.0) - addressable (~> 2.3) - faraday (>= 0.17.3, < 2.0) + signet (0.19.0) + addressable (~> 2.8) + faraday (>= 0.17.5, < 3.a) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.8) + simctl (1.6.10) CFPropertyList naturally - slack-notifier (2.3.2) + slack-notifier (2.4.0) terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) + trailblazer-option (0.1.2) tty-cursor (0.7.1) - tty-screen (0.8.1) + tty-screen (0.8.2) tty-spinner (0.9.3) tty-cursor (~> 0.7) uber (0.1.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.7) - unicode-display_width (1.7.0) - webrick (1.7.0) + unicode-display_width (1.8.0) word_wrap (1.0.0) - xcodeproj (1.19.0) + xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) - nanaimo (~> 0.3.0) + nanaimo (~> 0.4.0) + rexml (>= 3.3.6, < 4.0) xcpretty (0.3.0) rouge (~> 2.0.7) xcpretty-travis-formatter (1.0.1) xcpretty (~> 0.2, >= 0.0.7) PLATFORMS - x86_64-darwin-20 + arm64-darwin-22 DEPENDENCIES - bundler (~> 2.2.0) + bundler (~> 2.4.20) danger (~> 8.2.0) fastlane (~> 2.178.0) BUNDLED WITH - 2.2.5 + 2.4.20 From 3ec35e0316297039815ff48183122f830843a516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Mon, 10 Feb 2025 15:22:58 +0100 Subject: [PATCH 16/25] feat: bundler fix --- .bundler-version | 2 +- Gemfile | 2 +- Gemfile.lock | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bundler-version b/.bundler-version index 19244e8..9c84656 100644 --- a/.bundler-version +++ b/.bundler-version @@ -1 +1 @@ -2.4.21 \ No newline at end of file +2.4.13 \ No newline at end of file diff --git a/Gemfile b/Gemfile index 6ff18ff..5c13863 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ # STRV source 'https://rubygems.org' -gem 'bundler', '~> 2.4.20' +gem 'bundler', '~> 2.4.13' gem 'danger', '~> 8.2.0' gem 'fastlane', '~> 2.178.0' diff --git a/Gemfile.lock b/Gemfile.lock index a7e26a2..77e21ef 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -253,7 +253,7 @@ PLATFORMS arm64-darwin-22 DEPENDENCIES - bundler (~> 2.4.20) + bundler (~> 2.4.13) danger (~> 8.2.0) fastlane (~> 2.178.0) From 5c542cd391c3fcc00d5cdedc1fe7c238e1f8d867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Mon, 10 Feb 2025 15:27:42 +0100 Subject: [PATCH 17/25] feat: updated changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e84900b..b545202 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ __Sections__ - Container methods for registering and resolving dependencies were moved from extensions to the class body in order to make them overrideable - 'APPLICATION_EXTENSION_API_ONLY' flag was added in order to get rid of warnings in app extensions +### Added +- Support for async dependency injection ## [1.0.2] From 5da88e2ad586a785a82cd9a5e65f22ae0a6e829f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Wed, 12 Feb 2025 10:45:53 +0100 Subject: [PATCH 18/25] fix: line ending --- .bundler-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.bundler-version b/.bundler-version index 9c84656..b40e924 100644 --- a/.bundler-version +++ b/.bundler-version @@ -1 +1 @@ -2.4.13 \ No newline at end of file +2.4.13 From dc8dd203910217d40e1790d9d9bf0ea2e99520ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Tue, 18 Feb 2025 15:56:25 +0100 Subject: [PATCH 19/25] feat: bundler update --- .bundler-version | 2 +- Gemfile | 6 +-- Gemfile.lock | 110 ++++++++++++++++++++++++----------------------- 3 files changed, 60 insertions(+), 58 deletions(-) diff --git a/.bundler-version b/.bundler-version index b40e924..ec1cf33 100644 --- a/.bundler-version +++ b/.bundler-version @@ -1 +1 @@ -2.4.13 +2.6.3 diff --git a/Gemfile b/Gemfile index 5c13863..0310a9c 100644 --- a/Gemfile +++ b/Gemfile @@ -3,9 +3,9 @@ # STRV source 'https://rubygems.org' -gem 'bundler', '~> 2.4.13' +gem 'bundler', '~> 2.6.3' gem 'danger', '~> 8.2.0' -gem 'fastlane', '~> 2.178.0' +gem 'fastlane', '~> 2.217.0' # Novější verze podporující Ruby 3.x plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') -eval_gemfile(plugins_path) if File.exist?(plugins_path) +eval_gemfile(plugins_path) if File.exist?(plugins_path) \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 77e21ef..1e1ec5a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,8 +9,8 @@ GEM public_suffix (>= 2.0.2, < 7.0) artifactory (3.0.17) atomos (0.1.3) - aws-eventstream (1.3.0) - aws-partitions (1.1048.0) + aws-eventstream (1.3.1) + aws-partitions (1.1050.0) aws-sdk-core (3.218.1) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.992.0) @@ -20,7 +20,7 @@ GEM aws-sdk-kms (1.98.0) aws-sdk-core (~> 3, >= 3.216.0) aws-sigv4 (~> 1.5) - aws-sdk-s3 (1.180.0) + aws-sdk-s3 (1.181.0) aws-sdk-core (~> 3, >= 3.216.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.5) @@ -35,8 +35,8 @@ GEM open4 (~> 1.3) colored (1.2) colored2 (3.1.2) - commander-fastlane (4.4.6) - highline (~> 1.7.2) + commander (4.6.0) + highline (~> 2.0.0) cork (0.3.0) colored2 (~> 3.1) danger (8.2.3) @@ -90,15 +90,15 @@ GEM faraday_middleware (1.2.1) faraday (~> 1.0) fastimage (2.4.0) - fastlane (2.178.0) + fastlane (2.217.0) CFPropertyList (>= 2.3, < 4.0.0) - addressable (>= 2.3, < 3.0.0) + addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) aws-sdk-s3 (~> 1.0) babosa (>= 1.0.3, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) colored - commander-fastlane (>= 4.4.6, < 5.0.0) + commander (~> 4.6) dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 4.0) excon (>= 0.71.0, < 1.0.0) @@ -107,21 +107,23 @@ GEM faraday_middleware (~> 1.0) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.37.0, < 0.39.0) - google-cloud-storage (>= 1.15.0, < 2.0.0) - highline (>= 1.7.2, < 2.0.0) + google-apis-androidpublisher_v3 (~> 0.3) + google-apis-playcustomapp_v1 (~> 0.1) + google-cloud-storage (~> 1.31) + highline (~> 2.0) + http-cookie (~> 1.0.5) json (< 3.0.0) jwt (>= 2.1.0, < 3) mini_magick (>= 4.9.4, < 5.0.0) - multipart-post (~> 2.0.0) + multipart-post (>= 2.0.0, < 3.0.0) naturally (~> 2.2) + optparse (~> 0.1.1) plist (>= 3.1.0, < 4.0.0) rubyzip (>= 2.0.0, < 3.0.0) security (= 0.1.3) simctl (~> 1.6.3) - slack-notifier (>= 2.0.0, < 3.0.0) terminal-notifier (>= 2.0.0, < 3.0.0) - terminal-table (>= 1.4.5, < 2.0.0) + terminal-table (~> 3) tty-screen (>= 0.6.3, < 1.0.0) tty-spinner (>= 0.8.0, < 1.0.0) word_wrap (~> 1.0.0) @@ -132,64 +134,63 @@ GEM git (1.19.1) addressable (~> 2.8) rchardet (~> 1.8) - google-api-client (0.38.0) + google-apis-androidpublisher_v3 (0.76.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-core (0.16.0) addressable (~> 2.5, >= 2.5.1) - googleauth (~> 0.9) - httpclient (>= 2.8.1, < 3.0) - mini_mime (~> 1.0) - representable (~> 3.0) - retriable (>= 2.0, < 4.0) - signet (~> 0.12) - google-apis-core (0.11.3) - addressable (~> 2.5, >= 2.5.1) - googleauth (>= 0.16.2, < 2.a) - httpclient (>= 2.8.1, < 3.a) + googleauth (~> 1.9) + httpclient (>= 2.8.3, < 3.a) mini_mime (~> 1.0) + mutex_m representable (~> 3.0) retriable (>= 2.0, < 4.a) - rexml - google-apis-iamcredentials_v1 (0.17.0) - google-apis-core (>= 0.11.0, < 2.a) - google-apis-storage_v1 (0.31.0) - google-apis-core (>= 0.11.0, < 2.a) + google-apis-iamcredentials_v1 (0.22.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-playcustomapp_v1 (0.16.0) + google-apis-core (>= 0.15.0, < 2.a) + google-apis-storage_v1 (0.49.0) + google-apis-core (>= 0.15.0, < 2.a) google-cloud-core (1.7.1) google-cloud-env (>= 1.0, < 3.a) google-cloud-errors (~> 1.0) - google-cloud-env (2.1.1) + google-cloud-env (2.2.1) faraday (>= 1.0, < 3.a) google-cloud-errors (1.4.0) - google-cloud-storage (1.47.0) + google-cloud-storage (1.55.0) addressable (~> 2.8) digest-crc (~> 0.4) - google-apis-iamcredentials_v1 (~> 0.1) - google-apis-storage_v1 (~> 0.31.0) + google-apis-core (~> 0.13) + google-apis-iamcredentials_v1 (~> 0.18) + google-apis-storage_v1 (>= 0.42) google-cloud-core (~> 1.6) - googleauth (>= 0.16.2, < 2.a) + googleauth (~> 1.9) mini_mime (~> 1.0) - googleauth (0.17.1) - faraday (>= 0.17.3, < 2.0) + google-logging-utils (0.1.0) + googleauth (1.13.1) + faraday (>= 1.0, < 3.a) + google-cloud-env (~> 2.2) + google-logging-utils (~> 0.1) jwt (>= 1.4, < 3.0) - memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.15) - highline (1.7.10) + signet (>= 0.16, < 2.a) + highline (2.0.3) http-cookie (1.0.8) domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.2) - json (2.10.0) + json (2.10.1) jwt (2.10.1) base64 kramdown (2.5.1) rexml (>= 3.3.9) kramdown-parser-gfm (1.1.0) kramdown (~> 2.0) - memoist (0.16.2) mini_magick (4.13.2) mini_mime (1.1.5) multi_json (1.15.0) - multipart-post (2.0.0) + multipart-post (2.4.1) + mutex_m (0.3.0) nanaimo (0.4.0) nap (1.1.0) naturally (2.2.1) @@ -199,17 +200,18 @@ GEM faraday (>= 1, < 3) sawyer (~> 0.9) open4 (1.3.4) + optparse (0.1.1) os (1.1.4) plist (3.7.2) - public_suffix (5.1.1) + public_suffix (6.0.1) rake (13.2.1) - rchardet (1.8.0) + rchardet (1.9.0) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.4.0) + rexml (3.4.1) rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.4.1) @@ -225,17 +227,16 @@ GEM simctl (1.6.10) CFPropertyList naturally - slack-notifier (2.4.0) terminal-notifier (2.0.0) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) + terminal-table (3.0.2) + unicode-display_width (>= 1.1.1, < 3) trailblazer-option (0.1.2) tty-cursor (0.7.1) tty-screen (0.8.2) tty-spinner (0.9.3) tty-cursor (~> 0.7) uber (0.1.0) - unicode-display_width (1.8.0) + unicode-display_width (2.6.0) word_wrap (1.0.0) xcodeproj (1.27.0) CFPropertyList (>= 2.3.3, < 4.0) @@ -250,12 +251,13 @@ GEM xcpretty (~> 0.2, >= 0.0.7) PLATFORMS - arm64-darwin-22 + arm64-darwin-24 + ruby DEPENDENCIES - bundler (~> 2.4.13) + bundler (~> 2.6.3) danger (~> 8.2.0) - fastlane (~> 2.178.0) + fastlane (~> 2.217.0) BUNDLED WITH - 2.4.20 + 2.6.3 From 812cb70fc5bcddeb0f78a3a709f0e9f3655104d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Tue, 18 Feb 2025 16:03:37 +0100 Subject: [PATCH 20/25] fix: comment --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 0310a9c..5d607ca 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ source 'https://rubygems.org' gem 'bundler', '~> 2.6.3' gem 'danger', '~> 8.2.0' -gem 'fastlane', '~> 2.217.0' # Novější verze podporující Ruby 3.x +gem 'fastlane', '~> 2.217.0' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) \ No newline at end of file From 04e8c9efe3d629817f6a48af4dd078957ae0d26e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Tue, 18 Feb 2025 16:24:21 +0100 Subject: [PATCH 21/25] feat: bump fastlane version --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 5d607ca..36c8639 100644 --- a/Gemfile +++ b/Gemfile @@ -5,7 +5,7 @@ source 'https://rubygems.org' gem 'bundler', '~> 2.6.3' gem 'danger', '~> 8.2.0' -gem 'fastlane', '~> 2.217.0' +gem 'fastlane', '~> 2.226.0' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') eval_gemfile(plugins_path) if File.exist?(plugins_path) \ No newline at end of file From ce2133e0a474fc7a03662381f0134538e8a5a217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Wed, 19 Feb 2025 10:14:27 +0100 Subject: [PATCH 22/25] Update Sources/Container/Async/AsyncContainer.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Filip Haškovec --- Sources/Container/Async/AsyncContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Container/Async/AsyncContainer.swift b/Sources/Container/Async/AsyncContainer.swift index baa79b1..b09c9b2 100644 --- a/Sources/Container/Async/AsyncContainer.swift +++ b/Sources/Container/Async/AsyncContainer.swift @@ -32,7 +32,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin sharedInstances.removeAll() } - // MARK: Register dependency, Autoregister dependency + // MARK: Register dependency /// Register a dependency /// From ea58ced9de24f24df8856c3df9a36c095a5eeb18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Wed, 19 Feb 2025 10:15:02 +0100 Subject: [PATCH 23/25] Update Sources/Container/Async/AsyncContainer.swift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Filip Haškovec --- Sources/Container/Async/AsyncContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Container/Async/AsyncContainer.swift b/Sources/Container/Async/AsyncContainer.swift index b09c9b2..bbc51c2 100644 --- a/Sources/Container/Async/AsyncContainer.swift +++ b/Sources/Container/Async/AsyncContainer.swift @@ -54,7 +54,7 @@ public actor AsyncContainer: AsyncDependencyResolving, AsyncDependencyRegisterin sharedInstances[registration.identifier] = nil } - // MARK: Register dependency with argument, Autoregister dependency with argument + // MARK: Register dependency with argument /// Register a dependency with an argument /// From 480b4593f05e464df3b581ef97ba6d5c6cb1021c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Wed, 19 Feb 2025 10:30:13 +0100 Subject: [PATCH 24/25] fix: last line --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 36c8639..36cee07 100644 --- a/Gemfile +++ b/Gemfile @@ -8,4 +8,4 @@ gem 'danger', '~> 8.2.0' gem 'fastlane', '~> 2.226.0' plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') -eval_gemfile(plugins_path) if File.exist?(plugins_path) \ No newline at end of file +eval_gemfile(plugins_path) if File.exist?(plugins_path) From 2716660b91e52ae2de66dfa5d00ab5711c334f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20=C4=8Cech?= Date: Wed, 19 Feb 2025 11:48:11 +0100 Subject: [PATCH 25/25] fix: tests --- Tests/Container/Async/AsyncComplexTests.swift | 2 +- Tests/Container/Sync/ComplexTests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/Container/Async/AsyncComplexTests.swift b/Tests/Container/Async/AsyncComplexTests.swift index b2528b2..194fee3 100644 --- a/Tests/Container/Async/AsyncComplexTests.swift +++ b/Tests/Container/Async/AsyncComplexTests.swift @@ -44,7 +44,7 @@ final class AsyncComplexTests: AsyncDITestCase { resolvedDependency1 = nil - XCTAssertNil(resolvedDependency1, "Shared instance wasn't released") + XCTAssertNil(resolvedDependency2, "Shared instance wasn't released") } func testReregistration() async { diff --git a/Tests/Container/Sync/ComplexTests.swift b/Tests/Container/Sync/ComplexTests.swift index 779e3be..27c9b05 100644 --- a/Tests/Container/Sync/ComplexTests.swift +++ b/Tests/Container/Sync/ComplexTests.swift @@ -40,7 +40,7 @@ final class ComplexTests: DITestCase { resolvedDependency1 = nil - XCTAssertNil(resolvedDependency1, "Shared instance wasn't released") + XCTAssertNil(resolvedDependency2, "Shared instance wasn't released") } func testReregistration() {