Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add ServiceName struct to make named services safer #1

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Resolver.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -61,6 +62,7 @@
4C653ED1246EC80700131637 /* ResolverArgumentTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResolverArgumentTests.swift; sourceTree = "<group>"; };
4C9BFB0B2357D98800E6FB80 /* ResolverInjectedTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResolverInjectedTests.swift; sourceTree = "<group>"; };
4CA289B525771DF6000C6F95 /* ResolverCyclicDependencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverCyclicDependencyTests.swift; sourceTree = "<group>"; };
D7FFC5F5259B53EC00428EED /* TestServiceNames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestServiceNames.swift; sourceTree = "<group>"; };
OBJ_12 /* ResolverBasicTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverBasicTests.swift; sourceTree = "<group>"; };
OBJ_13 /* ResolverClassTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverClassTests.swift; sourceTree = "<group>"; };
OBJ_14 /* ResolverContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResolverContainerTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -129,6 +131,7 @@
4C9BFB0B2357D98800E6FB80 /* ResolverInjectedTests.swift */,
OBJ_22 /* TestData.swift */,
OBJ_23 /* XCTestManifests.swift */,
D7FFC5F5259B53EC00428EED /* TestServiceNames.swift */,
);
name = ResolverTests;
path = Tests/ResolverTests;
Expand Down Expand Up @@ -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 */,
Expand Down
64 changes: 36 additions & 28 deletions Sources/Resolver/Resolver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -107,7 +115,7 @@ public final class Resolver {
/// - returns: ResolverOptions instance that allows further customization of registered Service.
///
@discardableResult
public static func register<Service>(_ type: Service.Type = Service.self, name: String? = nil,
public static func register<Service>(_ type: Service.Type = Service.self, name: ServiceName? = nil,
factory: @escaping ResolverFactory<Service>) -> ResolverOptions<Service> {
return main.register(type, name: name, factory: factory)
}
Expand All @@ -121,7 +129,7 @@ public final class Resolver {
/// - returns: ResolverOptions instance that allows further customization of registered Service.
///
@discardableResult
public static func register<Service>(_ type: Service.Type = Service.self, name: String? = nil,
public static func register<Service>(_ type: Service.Type = Service.self, name: ServiceName? = nil,
factory: @escaping ResolverFactoryResolver<Service>) -> ResolverOptions<Service> {
return main.register(type, name: name, factory: factory)
}
Expand All @@ -135,7 +143,7 @@ public final class Resolver {
/// - returns: ResolverOptions instance that allows further customization of registered Service.
///
@discardableResult
public static func register<Service>(_ type: Service.Type = Service.self, name: String? = nil,
public static func register<Service>(_ type: Service.Type = Service.self, name: ServiceName? = nil,
factory: @escaping ResolverFactoryArgumentsN<Service>) -> ResolverOptions<Service> {
return main.register(type, name: name, factory: factory)
}
Expand All @@ -149,7 +157,7 @@ public final class Resolver {
/// - returns: ResolverOptions instance that allows further customization of registered Service.
///
@discardableResult
public final func register<Service>(_ type: Service.Type = Service.self, name: String? = nil,
public final func register<Service>(_ type: Service.Type = Service.self, name: ServiceName? = nil,
factory: @escaping ResolverFactory<Service>) -> ResolverOptions<Service> {
let key = ObjectIdentifier(Service.self).hashValue
let registration = ResolverRegistrationOnly(resolver: self, key: key, name: name, factory: factory)
Expand All @@ -166,7 +174,7 @@ public final class Resolver {
/// - returns: ResolverOptions instance that allows further customization of registered Service.
///
@discardableResult
public final func register<Service>(_ type: Service.Type = Service.self, name: String? = nil,
public final func register<Service>(_ type: Service.Type = Service.self, name: ServiceName? = nil,
factory: @escaping ResolverFactoryResolver<Service>) -> ResolverOptions<Service> {
let key = ObjectIdentifier(Service.self).hashValue
let registration = ResolverRegistrationResolver(resolver: self, key: key, name: name, factory: factory)
Expand All @@ -183,7 +191,7 @@ public final class Resolver {
/// - returns: ResolverOptions instance that allows further customization of registered Service.
///
@discardableResult
public final func register<Service>(_ type: Service.Type = Service.self, name: String? = nil,
public final func register<Service>(_ type: Service.Type = Service.self, name: ServiceName? = nil,
factory: @escaping ResolverFactoryArgumentsN<Service>) -> ResolverOptions<Service> {
let key = ObjectIdentifier(Service.self).hashValue
let registration = ResolverRegistrationArgumentsN(resolver: self, key: key, name: name, factory: factory)
Expand All @@ -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<Service>(_ type: Service.Type = Service.self, name: String? = nil, args: Any? = nil) -> Service {
public static func resolve<Service>(_ 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)
}
Expand All @@ -214,12 +222,12 @@ public final class Resolver {
///
/// - returns: Instance of specified Service.
///
public final func resolve<Service>(_ type: Service.Type = Service.self, name: String? = nil, args: Any? = nil) -> Service {
if let registration = lookup(type, name: name ?? NONAME),
public final func resolve<Service>(_ 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.
Expand All @@ -230,7 +238,7 @@ public final class Resolver {
///
/// - returns: Instance of specified Service.
///
public static func optional<Service>(_ type: Service.Type = Service.self, name: String? = nil, args: Any? = nil) -> Service? {
public static func optional<Service>(_ 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)
}
Expand All @@ -244,8 +252,8 @@ public final class Resolver {
///
/// - returns: Instance of specified Service.
///
public final func optional<Service>(_ type: Service.Type = Service.self, name: String? = nil, args: Any? = nil) -> Service? {
if let registration = lookup(type, name: name ?? NONAME),
public final func optional<Service>(_ 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
}
Expand All @@ -268,12 +276,12 @@ public final class Resolver {
}

/// Internal function adds a new registration to the proper container.
private final func add<Service>(registration: ResolverRegistration<Service>, with key: Int, name: String?) {
private final func add<Service>(registration: ResolverRegistration<Service>, 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]
}
}

Expand Down Expand Up @@ -372,7 +380,7 @@ public class ResolverOptions<Service> {
/// - returns: ResolverOptions instance that allows further customization of registered Service.
///
@discardableResult
public final func implements<Protocol>(_ type: Protocol.Type, name: String? = nil) -> ResolverOptions<Service> {
public final func implements<Protocol>(_ type: Protocol.Type, name: ServiceName? = nil) -> ResolverOptions<Service> {
resolver?.register(type.self, name: name) { r, _ in r.resolve(Service.self) as? Protocol }
return self
}
Expand Down Expand Up @@ -428,10 +436,10 @@ public class ResolverRegistration<Service>: ResolverOptions<Service> {
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)
}
Expand All @@ -449,7 +457,7 @@ public final class ResolverRegistrationOnly<Service>: ResolverRegistration<Servi

public var factory: ResolverFactory<Service>

public init(resolver: Resolver, key: Int, name: String?, factory: @escaping ResolverFactory<Service>) {
public init(resolver: Resolver, key: Int, name: ServiceName?, factory: @escaping ResolverFactory<Service>) {
self.factory = factory
super.init(resolver: resolver, key: key, name: name)
}
Expand All @@ -468,7 +476,7 @@ public final class ResolverRegistrationResolver<Service>: ResolverRegistration<S

public var factory: ResolverFactoryResolver<Service>

public init(resolver: Resolver, key: Int, name: String?, factory: @escaping ResolverFactoryResolver<Service>) {
public init(resolver: Resolver, key: Int, name: ServiceName?, factory: @escaping ResolverFactoryResolver<Service>) {
self.factory = factory
super.init(resolver: resolver, key: key, name: name)
}
Expand All @@ -487,7 +495,7 @@ public final class ResolverRegistrationArgumentsN<Service>: ResolverRegistration

public var factory: ResolverFactoryArgumentsN<Service>

public init(resolver: Resolver, key: Int, name: String?, factory: @escaping ResolverFactoryArgumentsN<Service>) {
public init(resolver: Resolver, key: Int, name: ServiceName?, factory: @escaping ResolverFactoryArgumentsN<Service>) {
self.factory = factory
super.init(resolver: resolver, key: key, name: name)
}
Expand Down Expand Up @@ -701,7 +709,7 @@ public struct Injected<Service> {
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 {
Expand All @@ -722,10 +730,10 @@ public struct Injected<Service> {
public struct LazyInjected<Service> {
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
}
Expand Down Expand Up @@ -756,7 +764,7 @@ public struct OptionalInjected<Service> {
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? {
Expand All @@ -783,7 +791,7 @@ public struct InjectedObject<Service>: 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 {
Expand Down
12 changes: 8 additions & 4 deletions Tests/ResolverTests/ResolverClassTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
import XCTest
@testable import Resolver

extension ServiceName {
static let props = Self("Props")
}

class ResolverClassTests: XCTestCase {

var resolver: Resolver!
Expand Down Expand Up @@ -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")
}
Expand Down
8 changes: 4 additions & 4 deletions Tests/ResolverTests/ResolverInjectedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"))
Expand Down
27 changes: 13 additions & 14 deletions Tests/ResolverTests/ResolverNameTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import XCTest
@testable import Resolver

class ResolverNameTests: XCTestCase {

var resolver: Resolver!

override func setUp() {
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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()

Expand Down
Loading