diff --git a/Resolver.xcodeproj/project.pbxproj b/Resolver.xcodeproj/project.pbxproj index ed0ebd8..40209b9 100644 --- a/Resolver.xcodeproj/project.pbxproj +++ b/Resolver.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 4C653ED2246EC80700131637 /* ResolverArgumentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C653ED1246EC80700131637 /* ResolverArgumentTests.swift */; }; 4C9BFB0C2357D98800E6FB80 /* ResolverInjectedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BFB0B2357D98800E6FB80 /* ResolverInjectedTests.swift */; }; 4CA289B625771DF6000C6F95 /* ResolverCyclicDependencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA289B525771DF6000C6F95 /* ResolverCyclicDependencyTests.swift */; }; + D7FFC5F6259B53EC00428EED /* TestServiceNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FFC5F5259B53EC00428EED /* TestServiceNames.swift */; }; OBJ_40 /* Resolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* Resolver.swift */; }; OBJ_47 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; }; OBJ_58 /* ResolverBasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* ResolverBasicTests.swift */; }; @@ -61,6 +62,7 @@ 4C653ED1246EC80700131637 /* ResolverArgumentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResolverArgumentTests.swift; sourceTree = ""; }; 4C9BFB0B2357D98800E6FB80 /* ResolverInjectedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResolverInjectedTests.swift; sourceTree = ""; }; 4CA289B525771DF6000C6F95 /* ResolverCyclicDependencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverCyclicDependencyTests.swift; sourceTree = ""; }; + D7FFC5F5259B53EC00428EED /* TestServiceNames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestServiceNames.swift; sourceTree = ""; }; OBJ_12 /* ResolverBasicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverBasicTests.swift; sourceTree = ""; }; OBJ_13 /* ResolverClassTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverClassTests.swift; sourceTree = ""; }; OBJ_14 /* ResolverContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverContainerTests.swift; sourceTree = ""; }; @@ -129,6 +131,7 @@ 4C9BFB0B2357D98800E6FB80 /* ResolverInjectedTests.swift */, OBJ_22 /* TestData.swift */, OBJ_23 /* XCTestManifests.swift */, + D7FFC5F5259B53EC00428EED /* TestServiceNames.swift */, ); name = ResolverTests; path = Tests/ResolverTests; @@ -287,6 +290,7 @@ OBJ_63 /* ResolverScopeNameTests.swift in Sources */, OBJ_64 /* ResolverScopeReferenceTests.swift in Sources */, OBJ_65 /* ResolverScopeValueTests.swift in Sources */, + D7FFC5F6259B53EC00428EED /* TestServiceNames.swift in Sources */, 4CA289B625771DF6000C6F95 /* ResolverCyclicDependencyTests.swift in Sources */, OBJ_66 /* ResolverStoryboardTests.swift in Sources */, OBJ_68 /* TestData.swift in Sources */, diff --git a/Sources/Resolver/Resolver.swift b/Sources/Resolver/Resolver.swift index 1be9061..b1e1874 100644 --- a/Sources/Resolver/Resolver.swift +++ b/Sources/Resolver/Resolver.swift @@ -51,6 +51,14 @@ extension Resolving { } } +public struct ServiceName { + let rawValue: String + + public init(_ rawValue: String) { + self.rawValue = rawValue + } +} + /// Resolver is a Dependency Injection registry that registers Services for later resolution and /// injection into newly constructed instances. public final class Resolver { @@ -107,7 +115,7 @@ public final class Resolver { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public static func register(_ type: Service.Type = Service.self, name: String? = nil, + public static func register(_ type: Service.Type = Service.self, name: ServiceName? = nil, factory: @escaping ResolverFactory) -> ResolverOptions { return main.register(type, name: name, factory: factory) } @@ -121,7 +129,7 @@ public final class Resolver { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public static func register(_ type: Service.Type = Service.self, name: String? = nil, + public static func register(_ type: Service.Type = Service.self, name: ServiceName? = nil, factory: @escaping ResolverFactoryResolver) -> ResolverOptions { return main.register(type, name: name, factory: factory) } @@ -135,7 +143,7 @@ public final class Resolver { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public static func register(_ type: Service.Type = Service.self, name: String? = nil, + public static func register(_ type: Service.Type = Service.self, name: ServiceName? = nil, factory: @escaping ResolverFactoryArgumentsN) -> ResolverOptions { return main.register(type, name: name, factory: factory) } @@ -149,7 +157,7 @@ public final class Resolver { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public final func register(_ type: Service.Type = Service.self, name: String? = nil, + public final func register(_ type: Service.Type = Service.self, name: ServiceName? = nil, factory: @escaping ResolverFactory) -> ResolverOptions { let key = ObjectIdentifier(Service.self).hashValue let registration = ResolverRegistrationOnly(resolver: self, key: key, name: name, factory: factory) @@ -166,7 +174,7 @@ public final class Resolver { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public final func register(_ type: Service.Type = Service.self, name: String? = nil, + public final func register(_ type: Service.Type = Service.self, name: ServiceName? = nil, factory: @escaping ResolverFactoryResolver) -> ResolverOptions { let key = ObjectIdentifier(Service.self).hashValue let registration = ResolverRegistrationResolver(resolver: self, key: key, name: name, factory: factory) @@ -183,7 +191,7 @@ public final class Resolver { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public final func register(_ type: Service.Type = Service.self, name: String? = nil, + public final func register(_ type: Service.Type = Service.self, name: ServiceName? = nil, factory: @escaping ResolverFactoryArgumentsN) -> ResolverOptions { let key = ObjectIdentifier(Service.self).hashValue let registration = ResolverRegistrationArgumentsN(resolver: self, key: key, name: name, factory: factory) @@ -200,7 +208,7 @@ public final class Resolver { /// - parameter args: Optional arguments that may be passed to registration factory. /// /// - returns: Instance of specified Service. - public static func resolve(_ type: Service.Type = Service.self, name: String? = nil, args: Any? = nil) -> Service { + public static func resolve(_ type: Service.Type = Service.self, name: ServiceName? = nil, args: Any? = nil) -> Service { Resolver.registerServices?() // always check initial registrations first in case registerAllServices swaps root return root.resolve(type, name: name, args: args) } @@ -214,12 +222,12 @@ public final class Resolver { /// /// - returns: Instance of specified Service. /// - public final func resolve(_ type: Service.Type = Service.self, name: String? = nil, args: Any? = nil) -> Service { - if let registration = lookup(type, name: name ?? NONAME), + public final func resolve(_ type: Service.Type = Service.self, name: ServiceName? = nil, args: Any? = nil) -> Service { + if let registration = lookup(type, name: name?.rawValue ?? NONAME), let service = registration.scope.resolve(resolver: self, registration: registration, args: args) { return service } - fatalError("RESOLVER: '\(Service.self):\(name ?? "")' not resolved. To disambiguate optionals use resover.optional().") + fatalError("RESOLVER: '\(Service.self):\(name?.rawValue ?? "")' not resolved. To disambiguate optionals use resover.optional().") } /// Static function calls the root registry to resolve an optional Service type. @@ -230,7 +238,7 @@ public final class Resolver { /// /// - returns: Instance of specified Service. /// - public static func optional(_ type: Service.Type = Service.self, name: String? = nil, args: Any? = nil) -> Service? { + public static func optional(_ type: Service.Type = Service.self, name: ServiceName? = nil, args: Any? = nil) -> Service? { Resolver.registerServices?() // always check initial registrations first in case registerAllServices swaps root return root.optional(type, name: name, args: args) } @@ -244,8 +252,8 @@ public final class Resolver { /// /// - returns: Instance of specified Service. /// - public final func optional(_ type: Service.Type = Service.self, name: String? = nil, args: Any? = nil) -> Service? { - if let registration = lookup(type, name: name ?? NONAME), + public final func optional(_ type: Service.Type = Service.self, name: ServiceName? = nil, args: Any? = nil) -> Service? { + if let registration = lookup(type, name: name?.rawValue ?? NONAME), let service = registration.scope.resolve(resolver: self, registration: registration, args: args) { return service } @@ -268,12 +276,12 @@ public final class Resolver { } /// Internal function adds a new registration to the proper container. - private final func add(registration: ResolverRegistration, with key: Int, name: String?) { + private final func add(registration: ResolverRegistration, with key: Int, name: ServiceName?) { if var container = registrations[key] { - container[name ?? NONAME] = registration + container[name?.rawValue ?? NONAME] = registration registrations[key] = container } else { - registrations[key] = [name ?? NONAME : registration] + registrations[key] = [name?.rawValue ?? NONAME : registration] } } @@ -372,7 +380,7 @@ public class ResolverOptions { /// - returns: ResolverOptions instance that allows further customization of registered Service. /// @discardableResult - public final func implements(_ type: Protocol.Type, name: String? = nil) -> ResolverOptions { + public final func implements(_ type: Protocol.Type, name: ServiceName? = nil) -> ResolverOptions { resolver?.register(type.self, name: name) { r, _ in r.resolve(Service.self) as? Protocol } return self } @@ -428,10 +436,10 @@ public class ResolverRegistration: ResolverOptions { public var key: Int public var cacheKey: String - public init(resolver: Resolver, key: Int, name: String?) { + public init(resolver: Resolver, key: Int, name: ServiceName?) { self.key = key - if let namedService = name { - self.cacheKey = String(key) + ":" + namedService + if let serviceName = name?.rawValue { + self.cacheKey = String(key) + ":" + serviceName } else { self.cacheKey = String(key) } @@ -449,7 +457,7 @@ public final class ResolverRegistrationOnly: ResolverRegistration - public init(resolver: Resolver, key: Int, name: String?, factory: @escaping ResolverFactory) { + public init(resolver: Resolver, key: Int, name: ServiceName?, factory: @escaping ResolverFactory) { self.factory = factory super.init(resolver: resolver, key: key, name: name) } @@ -468,7 +476,7 @@ public final class ResolverRegistrationResolver: ResolverRegistration - public init(resolver: Resolver, key: Int, name: String?, factory: @escaping ResolverFactoryResolver) { + public init(resolver: Resolver, key: Int, name: ServiceName?, factory: @escaping ResolverFactoryResolver) { self.factory = factory super.init(resolver: resolver, key: key, name: name) } @@ -487,7 +495,7 @@ public final class ResolverRegistrationArgumentsN: ResolverRegistration public var factory: ResolverFactoryArgumentsN - public init(resolver: Resolver, key: Int, name: String?, factory: @escaping ResolverFactoryArgumentsN) { + public init(resolver: Resolver, key: Int, name: ServiceName?, factory: @escaping ResolverFactoryArgumentsN) { self.factory = factory super.init(resolver: resolver, key: key, name: name) } @@ -701,7 +709,7 @@ public struct Injected { public init() { self.service = Resolver.resolve(Service.self) } - public init(name: String? = nil, container: Resolver? = nil) { + public init(name: ServiceName? = nil, container: Resolver? = nil) { self.service = container?.resolve(Service.self, name: name) ?? Resolver.resolve(Service.self, name: name) } public var wrappedValue: Service { @@ -722,10 +730,10 @@ public struct Injected { public struct LazyInjected { private var service: Service! public var container: Resolver? - public var name: String? + public var name: ServiceName? public var args: Any? public init() {} - public init(name: String? = nil, container: Resolver? = nil) { + public init(name: ServiceName? = nil, container: Resolver? = nil) { self.name = name self.container = container } @@ -756,7 +764,7 @@ public struct OptionalInjected { public init() { self.service = Resolver.optional(Service.self) } - public init(name: String? = nil, container: Resolver? = nil) { + public init(name: ServiceName? = nil, container: Resolver? = nil) { self.service = container?.optional(Service.self, name: name) ?? Resolver.optional(Service.self, name: name) } public var wrappedValue: Service? { @@ -783,7 +791,7 @@ public struct InjectedObject: DynamicProperty where Service: Observable public init() { self.service = Resolver.resolve(Service.self) } - public init(name: String? = nil, container: Resolver? = nil) { + public init(name: ServiceName? = nil, container: Resolver? = nil) { self.service = container?.resolve(Service.self, name: name) ?? Resolver.resolve(Service.self, name: name) } public var wrappedValue: Service { diff --git a/Tests/ResolverTests/ResolverClassTests.swift b/Tests/ResolverTests/ResolverClassTests.swift index 1ace948..9302bc4 100644 --- a/Tests/ResolverTests/ResolverClassTests.swift +++ b/Tests/ResolverTests/ResolverClassTests.swift @@ -9,6 +9,10 @@ import XCTest @testable import Resolver +extension ServiceName { + static let props = Self("Props") +} + class ResolverClassTests: XCTestCase { var resolver: Resolver! @@ -65,12 +69,12 @@ class ResolverClassTests: XCTestCase { XCTAssertNotNil(service?.session) } - func testRegistrationAndResolutionProperties() { - Resolver.register(name: "Props") { XYZSessionService() } + func testRegistrationAndResolutionProperties() { + Resolver.register(name: .props) { XYZSessionService() } .resolveProperties { (r, s) in s.name = "updated" - } - let session: XYZSessionService? = Resolver.optional(name: "Props") + } + let session: XYZSessionService? = Resolver.optional(name: .props) XCTAssertNotNil(session) XCTAssert(session?.name == "updated") } diff --git a/Tests/ResolverTests/ResolverInjectedTests.swift b/Tests/ResolverTests/ResolverInjectedTests.swift index 7eed38f..7259745 100644 --- a/Tests/ResolverTests/ResolverInjectedTests.swift +++ b/Tests/ResolverTests/ResolverInjectedTests.swift @@ -16,11 +16,11 @@ class BasicInjectedViewController { } class NamedInjectedViewController { - @Injected(name: "fred") var service: XYZNameService + @Injected(name: .fred) var service: XYZNameService } class NamedInjectedViewController2 { - @Injected(name: "barney") var service: XYZNameService + @Injected(name: .barney) var service: XYZNameService } extension Resolver { @@ -58,8 +58,8 @@ class ResolverInjectedTests: XCTestCase { Resolver.main.register { XYZSessionService() } Resolver.main.register { XYZService(Resolver.main.optional()) } - Resolver.main.register(name: "fred") { XYZNameService("fred") } - Resolver.main.register(name: "barney") { XYZNameService("barney") } + Resolver.main.register(name: .fred) { XYZNameService("fred") } + Resolver.main.register(name: .barney) { XYZNameService("barney") } Resolver.main.register { (_, args) in XYZArgumentService(condition: args("condition"), string: args("string")) diff --git a/Tests/ResolverTests/ResolverNameTests.swift b/Tests/ResolverTests/ResolverNameTests.swift index 3cee082..d67750c 100644 --- a/Tests/ResolverTests/ResolverNameTests.swift +++ b/Tests/ResolverTests/ResolverNameTests.swift @@ -10,7 +10,6 @@ import XCTest @testable import Resolver class ResolverNameTests: XCTestCase { - var resolver: Resolver! override func setUp() { @@ -24,11 +23,11 @@ class ResolverNameTests: XCTestCase { func testResolverValidNames() { - resolver.register(name: "Fred") { XYZNameService("Fred") } - resolver.register(name: "Barney") { XYZNameService("Barney") } + resolver.register(name: .fred) { XYZNameService("Fred") } + resolver.register(name: .barney) { XYZNameService("Barney") } - let fred: XYZNameService? = resolver.optional(name: "Fred") - let barney: XYZNameService? = resolver.optional(name: "Barney") + let fred: XYZNameService? = resolver.optional(name: .fred) + let barney: XYZNameService? = resolver.optional(name: .barney) // Check all services resolved XCTAssertNotNil(fred) @@ -41,22 +40,22 @@ class ResolverNameTests: XCTestCase { func testResolverInvalidNames() { - resolver.register(name: "Fred") { XYZNameService("Fred") } - resolver.register(name: "Barney") { XYZNameService("Barney") } + resolver.register(name: .fred) { XYZNameService("Fred") } + resolver.register(name: .barney) { XYZNameService("Barney") } - let wilma: XYZNameService? = resolver.optional(name: "Wilma") + let wilma: XYZNameService? = resolver.optional(name: .wilma) XCTAssertNil(wilma) } func testResolverNamesWithBaseService() { - resolver.register(name: "Fred") { XYZNameService("Fred") } - resolver.register(name: "Barney") { XYZNameService("Barney") } + resolver.register(name: .fred) { XYZNameService("Fred") } + resolver.register(name: .barney) { XYZNameService("Barney") } resolver.register() { XYZNameService("Base") } - let fred: XYZNameService? = resolver.optional(name: "Fred") - let barney: XYZNameService? = resolver.optional(name: "Barney") + let fred: XYZNameService? = resolver.optional(name: .fred) + let barney: XYZNameService? = resolver.optional(name: .barney) let base: XYZNameService? = resolver.optional() // Check all services resolved @@ -72,8 +71,8 @@ class ResolverNameTests: XCTestCase { func testResolverNamesWithNoBaseService() { - resolver.register(name: "Fred") { XYZNameService("Fred") } - resolver.register(name: "Barney") { XYZNameService("Barney") } + resolver.register(name: .fred) { XYZNameService("Fred") } + resolver.register(name: .barney) { XYZNameService("Barney") } let base: XYZNameService? = resolver.optional() diff --git a/Tests/ResolverTests/ResolverScopeNameTests.swift b/Tests/ResolverTests/ResolverScopeNameTests.swift index 252b462..5162d18 100644 --- a/Tests/ResolverTests/ResolverScopeNameTests.swift +++ b/Tests/ResolverTests/ResolverScopeNameTests.swift @@ -23,11 +23,11 @@ class ResolverScopeNameTests: XCTestCase { } func testResolverScopeNameGraph() { - resolver.register(name: "Fred") { XYZNameService("Fred") } - resolver.register(name: "Barney") { XYZNameService("Barney") } - let service1: XYZNameService? = resolver.optional(name: "Fred") - let service2: XYZNameService? = resolver.optional(name: "Barney") - let service3: XYZNameService? = resolver.optional(name: "Barney") + resolver.register(name: .fred) { XYZNameService("Fred") } + resolver.register(name: .barney) { XYZNameService("Barney") } + let service1: XYZNameService? = resolver.optional(name: .fred) + let service2: XYZNameService? = resolver.optional(name: .barney) + let service3: XYZNameService? = resolver.optional(name: .barney) XCTAssertNotNil(service1) XCTAssertNotNil(service2) XCTAssertNotNil(service3) @@ -43,11 +43,11 @@ class ResolverScopeNameTests: XCTestCase { } func testResolverScopeNameShared() { - resolver.register(name: "Fred") { XYZNameService("Fred") }.scope(Resolver.shared) - resolver.register(name: "Barney") { XYZNameService("Barney") }.scope(Resolver.shared) - let service1: XYZNameService? = resolver.optional(name: "Fred") - let service2: XYZNameService? = resolver.optional(name: "Barney") - let service3: XYZNameService? = resolver.optional(name: "Barney") + resolver.register(name: .fred) { XYZNameService("Fred") }.scope(Resolver.shared) + resolver.register(name: .barney) { XYZNameService("Barney") }.scope(Resolver.shared) + let service1: XYZNameService? = resolver.optional(name: .fred) + let service2: XYZNameService? = resolver.optional(name: .barney) + let service3: XYZNameService? = resolver.optional(name: .barney) XCTAssertNotNil(service1) XCTAssertNotNil(service2) XCTAssertNotNil(service3) @@ -63,11 +63,11 @@ class ResolverScopeNameTests: XCTestCase { } func testResolverScopeNameApplication() { - resolver.register(name: "Fred") { XYZNameService("Fred") }.scope(Resolver.application) - resolver.register(name: "Barney") { XYZNameService("Barney") }.scope(Resolver.application) - let service1: XYZNameService? = resolver.optional(name: "Fred") - let service2: XYZNameService? = resolver.optional(name: "Barney") - let service3: XYZNameService? = resolver.optional(name: "Barney") + resolver.register(name: .fred) { XYZNameService("Fred") }.scope(Resolver.application) + resolver.register(name: .barney) { XYZNameService("Barney") }.scope(Resolver.application) + let service1: XYZNameService? = resolver.optional(name: .fred) + let service2: XYZNameService? = resolver.optional(name: .barney) + let service3: XYZNameService? = resolver.optional(name: .barney) XCTAssertNotNil(service1) XCTAssertNotNil(service2) XCTAssertNotNil(service3) @@ -83,11 +83,11 @@ class ResolverScopeNameTests: XCTestCase { } func testResolverScopeNameCached() { - resolver.register(name: "Fred") { XYZNameService("Fred") }.scope(Resolver.cached) - resolver.register(name: "Barney") { XYZNameService("Barney") }.scope(Resolver.cached) - let service1: XYZNameService? = resolver.optional(name: "Fred") - let service2: XYZNameService? = resolver.optional(name: "Barney") - let service3: XYZNameService? = resolver.optional(name: "Barney") + resolver.register(name: .fred) { XYZNameService("Fred") }.scope(Resolver.cached) + resolver.register(name: .barney) { XYZNameService("Barney") }.scope(Resolver.cached) + let service1: XYZNameService? = resolver.optional(name: .fred) + let service2: XYZNameService? = resolver.optional(name: .barney) + let service3: XYZNameService? = resolver.optional(name: .barney) XCTAssertNotNil(service1) XCTAssertNotNil(service2) XCTAssertNotNil(service3) @@ -103,11 +103,11 @@ class ResolverScopeNameTests: XCTestCase { } func testResolverScopeNameUnique() { - resolver.register(name: "Fred") { XYZNameService("Fred") }.scope(Resolver.unique) - resolver.register(name: "Barney") { XYZNameService("Barney") }.scope(Resolver.unique) - let service1: XYZNameService? = resolver.optional(name: "Fred") - let service2: XYZNameService? = resolver.optional(name: "Barney") - let service3: XYZNameService? = resolver.optional(name: "Barney") + resolver.register(name: .fred) { XYZNameService("Fred") }.scope(Resolver.unique) + resolver.register(name: .barney) { XYZNameService("Barney") }.scope(Resolver.unique) + let service1: XYZNameService? = resolver.optional(name: .fred) + let service2: XYZNameService? = resolver.optional(name: .barney) + let service3: XYZNameService? = resolver.optional(name: .barney) XCTAssertNotNil(service1) XCTAssertNotNil(service2) XCTAssertNotNil(service3) diff --git a/Tests/ResolverTests/TestServiceNames.swift b/Tests/ResolverTests/TestServiceNames.swift new file mode 100644 index 0000000..a0ce1f2 --- /dev/null +++ b/Tests/ResolverTests/TestServiceNames.swift @@ -0,0 +1,14 @@ +// +// TestServiceNames.swift +// ResolverTests +// +// Created by Artem Kirienko on 29.12.2020. +// + +@testable import Resolver + +extension ServiceName { + static let fred = Self("Fred") + static let barney = Self("Barney") + static let wilma = Self("Wilma") +}