Skip to content

Commit cf40012

Browse files
committed
Create a class based Knit Resolver
1 parent 69fb56d commit cf40012

11 files changed

+89
-91
lines changed

Sources/Knit/Container.swift

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,11 @@ import Swinject
1111

1212
The Knit.Container also performs the function of a weak wrapper of the `Swinject.Container`.
1313
*/
14-
public class Container<TargetResolver>: Knit.Resolver {
14+
public class Container<TargetResolver: Resolver> {
1515

1616
// MARK: - Knit.Resolver
1717

18-
public var resolver: TargetResolver {
19-
self as! TargetResolver
20-
}
21-
22-
/// Returns `true` if the backing container is still available in memory, otherwise `false`.
23-
public var isAvailable: Bool {
24-
_swinjectContainer != nil
25-
}
26-
27-
// MARK: - Swinject.Resolver
28-
29-
public func unsafeResolver(file: StaticString, function: StaticString, line: UInt) -> Swinject.Resolver {
30-
_unwrappedSwinjectContainer(file: file, function: function, line: line)
31-
}
18+
public let resolver: TargetResolver
3219

3320
// MARK: - Private Properties
3421

@@ -39,6 +26,7 @@ public class Container<TargetResolver>: Knit.Resolver {
3926
// This should not be promoted from `fileprivate` access level.
4027
fileprivate init(_swinjectContainer: Swinject.Container) {
4128
self._swinjectContainer = _swinjectContainer
29+
self.resolver = TargetResolver(_swinjectContainer: _swinjectContainer)
4230
}
4331
}
4432

Sources/Knit/Module/ModuleAssembly.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import Swinject
77

88
public protocol ModuleAssembly<TargetResolver> {
99

10-
associatedtype TargetResolver
10+
associatedtype TargetResolver: Resolver
1111

1212
static var resolverType: Self.TargetResolver.Type { get }
1313

@@ -40,9 +40,8 @@ public extension ModuleAssembly {
4040
static var replaces: [any ModuleAssembly.Type] { [] }
4141

4242
static func scoped(_ dependencies: [any ModuleAssembly.Type]) -> [any ModuleAssembly.Type] {
43-
return dependencies.filter {
44-
// Default the scoped implementation to match types directly
45-
return self.resolverType == $0.resolverType
43+
return dependencies.filter { module in
44+
return resolverType.inherits(from: module.resolverType)
4645
}
4746
}
4847

Sources/Knit/Module/ScopedModuleAssembler.swift

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import Foundation
66
import Swinject
77

88
/// Module assembly which only allows registering assemblies which target a particular resolver type.
9-
public final class ScopedModuleAssembler<TargetResolver> {
9+
public final class ScopedModuleAssembler<TargetResolver: Resolver> {
1010

1111
public let internalAssembler: ModuleAssembler
1212

@@ -58,18 +58,6 @@ public final class ScopedModuleAssembler<TargetResolver> {
5858
behaviors: [Behavior] = [],
5959
postAssemble: ((Container<TargetResolver>) -> Void)? = nil
6060
) throws {
61-
// For provided modules, fail early if they are scoped incorrectly
62-
for assembly in modules {
63-
let moduleAssemblyType = type(of: assembly)
64-
if moduleAssemblyType.resolverType != TargetResolver.self {
65-
let scopingError = ScopedModuleAssemblerError.incorrectTargetResolver(
66-
expected: String(describing: TargetResolver.self),
67-
actual: String(describing: moduleAssemblyType.resolverType)
68-
)
69-
70-
throw DependencyBuilderError.assemblyValidationFailure(moduleAssemblyType, reason: scopingError)
71-
}
72-
}
7361
self.internalAssembler = try ModuleAssembler(
7462
parent: parent,
7563
_modules: modules,

Sources/Knit/Resolver.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,50 @@ public protocol Resolver: AnyObject {
1313

1414
func unsafeResolver(file: StaticString, function: StaticString, line: UInt) -> Swinject.Resolver
1515

16+
init(_swinjectContainer: Swinject.Container)
17+
18+
static func inherits(from resolverType: Resolver.Type) -> Bool
19+
20+
}
21+
22+
/// Default Resolver implementation. Designed to be inherited from
23+
open class RealResolver: Resolver {
24+
25+
private weak var _swinjectContainer: Swinject.Container?
26+
27+
/// Returns `true` if the backing container is still available in memory, otherwise `false`.
28+
public var isAvailable: Bool {
29+
_swinjectContainer != nil
30+
}
31+
32+
// MARK: - Swinject.Resolver
33+
34+
public func unsafeResolver(file: StaticString, function: StaticString, line: UInt) -> Swinject.Resolver {
35+
_unwrappedSwinjectContainer(file: file, function: function, line: line)
36+
}
37+
38+
public required init(_swinjectContainer: Swinject.Container) {
39+
self._swinjectContainer = _swinjectContainer
40+
}
41+
42+
/// Default implementation uses pure equality
43+
public class func inherits(from resolverType: Resolver.Type) -> Bool {
44+
return self == resolverType
45+
}
46+
47+
// Force unwraps the weak Container
48+
func _unwrappedSwinjectContainer(
49+
file: StaticString = #fileID,
50+
function: StaticString = #function,
51+
line: UInt = #line
52+
) -> Swinject.Container {
53+
guard let _swinjectContainer else {
54+
fatalError(
55+
"\(function) incorrectly accessed the container for \(self) which has already been released",
56+
file: file,
57+
line: line
58+
)
59+
}
60+
return _swinjectContainer
61+
}
1662
}

Sources/Knit/ServiceCollection/Container+ServiceCollection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ extension Container {
3030
name: makeUniqueCollectionRegistrationName(),
3131
factory: { r in
3232
MainActor.assumeIsolated {
33-
let resolver = r.resolve(Container<TargetResolver>.self)! as! TargetResolver
33+
let resolver = r.resolve(Container<TargetResolver>.self)!.resolver
3434
return factory(resolver)
3535
}
3636
}

Sources/Knit/ServiceCollection/Resolver+ServiceCollection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ extension Swinject.Resolver {
2929

3030
// MARK: - Knit Resolver
3131

32-
extension Knit.Resolver {
32+
extension RealResolver {
3333

3434
/// Resolves a collection of all services registered using
3535
/// ``Container/registerIntoCollection(_:factory:)``

Tests/KnitTests/ModuleAssemblyScopingTests.swift

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,13 @@ final class ModuleAssemblyScopingTests: XCTestCase {
2323

2424
}
2525

26-
private protocol ParentResolver: Resolver {}
27-
private protocol ChildResolver: Resolver {}
28-
private protocol OtherResolver: Resolver {}
29-
30-
extension ChildResolver {
31-
static func contains(resolver: Resolver.Type) -> Bool {
32-
return resolver == self || resolver == ParentResolver.self
26+
private class ParentResolver: RealResolver {}
27+
private class ChildResolver: ParentResolver {
28+
public override class func inherits(from resolverType: Resolver.Type) -> Bool {
29+
return resolverType == self || resolverType == ParentResolver.self
3330
}
3431
}
32+
private class OtherResolver: RealResolver {}
3533

3634
private struct Assembly1: GeneratedModuleAssembly {
3735
typealias TargetResolver = ParentResolver
@@ -56,15 +54,3 @@ private struct Assembly4: GeneratedModuleAssembly {
5654
static var generatedDependencies: [any ModuleAssembly.Type] { [Assembly1.self] }
5755
func assemble(container: Container<Self.TargetResolver>) {}
5856
}
59-
60-
private extension ModuleAssembly {
61-
// Override the default scoping function to allow assemblies using ParentResolver to be included in ChildResolver
62-
static func scoped(_ dependencies: [any ModuleAssembly.Type]) -> [any ModuleAssembly.Type] {
63-
return dependencies.filter {
64-
if self.resolverType == ChildResolver.self && $0.resolverType == ParentResolver.self {
65-
return true
66-
}
67-
return self.resolverType == $0.resolverType
68-
}
69-
}
70-
}

Tests/KnitTests/ScopedModuleAssemblerTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private struct Assembly1: AutoInitModuleAssembly {
6666
func assemble(container: Knit.Container<Self.TargetResolver>) { }
6767
}
6868

69-
protocol OutsideResolver: Swinject.Resolver { }
69+
class OutsideResolver: RealResolver { }
7070

7171
private struct Assembly2: AutoInitModuleAssembly {
7272
typealias TargetResolver = OutsideResolver

Tests/KnitTests/ServiceCollectorTests.swift

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,9 @@
66
import Swinject
77
import XCTest
88

9-
private protocol ParentResolver: Knit.Resolver {}
10-
private protocol ChildResolver: ParentResolver {}
11-
private protocol GrandChildResolver: ChildResolver {}
12-
13-
extension Knit.Container: ParentResolver, ChildResolver, GrandChildResolver {}
9+
private class ParentResolver: RealResolver {}
10+
private class ChildResolver: ParentResolver {}
11+
private class GrandChildResolver: ChildResolver {}
1412

1513
private protocol ServiceProtocol {}
1614

@@ -87,7 +85,7 @@ final class ServiceCollectorTests: XCTestCase {
8785
@MainActor
8886
func test_registerIntoCollection() {
8987
let swinjectContainer = Swinject.Container()
90-
let container = ContainerManager(swinjectContainer: swinjectContainer).register(Any.self)
88+
let container = ContainerManager(swinjectContainer: swinjectContainer).register(ParentResolver.self)
9189
container._unwrappedSwinjectContainer().addBehavior(ServiceCollector())
9290

9391
// Register some services into a collection
@@ -99,12 +97,12 @@ final class ServiceCollectorTests: XCTestCase {
9997
container.registerIntoCollection(CustomService.self) { _ in CustomService(name: "Custom 2") }
10098

10199
// Resolving each collection should produce the expected services
102-
let serviceProtocolCollection = container.resolveCollection(ServiceProtocol.self)
100+
let serviceProtocolCollection = container.resolver.resolveCollection(ServiceProtocol.self)
103101
XCTAssertEqual(serviceProtocolCollection.entries.count, 2)
104102
XCTAssert(serviceProtocolCollection.entries.first is ServiceA)
105103
XCTAssert(serviceProtocolCollection.entries.last is ServiceB)
106104

107-
let customServiceCollection = container.resolveCollection(CustomService.self)
105+
let customServiceCollection = container.resolver.resolveCollection(CustomService.self)
108106
XCTAssertEqual(
109107
customServiceCollection.entries.map(\.name),
110108
["Custom 1", "Custom 2"]
@@ -114,19 +112,19 @@ final class ServiceCollectorTests: XCTestCase {
114112
@MainActor
115113
func test_registerIntoCollection_emptyWithBehavior() {
116114
let swinjectContainer = Swinject.Container()
117-
let container = ContainerManager(swinjectContainer: swinjectContainer).register(Any.self)
115+
let container = ContainerManager(swinjectContainer: swinjectContainer).register(ParentResolver.self)
118116
container._unwrappedSwinjectContainer().addBehavior(ServiceCollector())
119117

120-
let collection = container.resolveCollection(ServiceProtocol.self)
118+
let collection = container.resolver.resolveCollection(ServiceProtocol.self)
121119
XCTAssertEqual(collection.entries.count, 0)
122120
}
123121

124122
@MainActor
125123
func test_registerIntoCollection_emptyWithoutBehavior() {
126124
let swinjectContainer = Swinject.Container()
127-
let container = ContainerManager(swinjectContainer: swinjectContainer).register(Any.self)
125+
let container = ContainerManager(swinjectContainer: swinjectContainer).register(ParentResolver.self)
128126

129-
let collection = container.resolveCollection(ServiceProtocol.self)
127+
let collection = container.resolver.resolveCollection(ServiceProtocol.self)
130128
XCTAssertEqual(collection.entries.count, 0)
131129
}
132130

@@ -135,7 +133,7 @@ final class ServiceCollectorTests: XCTestCase {
135133
@MainActor
136134
func test_registerIntoCollection_doesntConflictWithArray() throws {
137135
let swinjectContainer = Swinject.Container()
138-
let container = ContainerManager(swinjectContainer: swinjectContainer).register(Any.self)
136+
let container = ContainerManager(swinjectContainer: swinjectContainer).register(ParentResolver.self)
139137
container._unwrappedSwinjectContainer().addBehavior(ServiceCollector())
140138

141139
// Register A into a collection
@@ -145,7 +143,7 @@ final class ServiceCollectorTests: XCTestCase {
145143
container.register([ServiceProtocol].self) { _ in [ServiceB()] }
146144

147145
// Resolving the collection should produce A
148-
let collection = container.resolveCollection(ServiceProtocol.self)
146+
let collection = container.resolver.resolveCollection(ServiceProtocol.self)
149147
XCTAssertEqual(collection.entries.count, 1)
150148
XCTAssert(collection.entries.first is ServiceA)
151149

@@ -158,7 +156,7 @@ final class ServiceCollectorTests: XCTestCase {
158156
@MainActor
159157
func test_registerIntoCollection_doesntImplicitlyAggregateInstances() throws {
160158
let swinjectContainer = Swinject.Container()
161-
let container = ContainerManager(swinjectContainer: swinjectContainer).register(Any.self)
159+
let container = ContainerManager(swinjectContainer: swinjectContainer).register(ParentResolver.self)
162160
container._unwrappedSwinjectContainer().addBehavior(ServiceCollector())
163161

164162
// Register A and B into a collection
@@ -169,7 +167,7 @@ final class ServiceCollectorTests: XCTestCase {
169167
_ = container.register(ServiceProtocol.self) { _ in ServiceB() }
170168

171169
// Resolving the collection should produce A and B
172-
let collection = container.resolveCollection(ServiceProtocol.self)
170+
let collection = container.resolver.resolveCollection(ServiceProtocol.self)
173171
XCTAssertEqual(collection.entries.count, 2)
174172
XCTAssert(collection.entries.first is ServiceA)
175173
XCTAssert(collection.entries.last is ServiceB)
@@ -181,7 +179,7 @@ final class ServiceCollectorTests: XCTestCase {
181179
@MainActor
182180
func test_registerIntoCollection_allowsDuplicates() {
183181
let swinjectContainer = Swinject.Container()
184-
let container = ContainerManager(swinjectContainer: swinjectContainer).register(Any.self)
182+
let container = ContainerManager(swinjectContainer: swinjectContainer).register(ParentResolver.self)
185183
container._unwrappedSwinjectContainer().addBehavior(ServiceCollector())
186184

187185
// Register some duplicate services
@@ -190,7 +188,7 @@ final class ServiceCollectorTests: XCTestCase {
190188
_ = container.registerIntoCollection(ServiceProtocol.self) { _ in CustomService(name: "Car Repair") }
191189

192190
// Resolving the collection should produce all services
193-
let collection = container.resolveCollection(ServiceProtocol.self)
191+
let collection = container.resolver.resolveCollection(ServiceProtocol.self)
194192
XCTAssertEqual(
195193
collection.entries.compactMap { ($0 as? CustomService)?.name },
196194
["Dry Cleaning", "Car Repair", "Car Repair"]
@@ -202,7 +200,7 @@ final class ServiceCollectorTests: XCTestCase {
202200
@MainActor
203201
func test_registerIntoCollection_supportsTransientScopedObjects() throws {
204202
let swinjectContainer = Swinject.Container()
205-
let container = ContainerManager(swinjectContainer: swinjectContainer).register(Any.self)
203+
let container = ContainerManager(swinjectContainer: swinjectContainer).register(ParentResolver.self)
206204
container._unwrappedSwinjectContainer().addBehavior(ServiceCollector())
207205

208206
// Register a service with the `transient` scope.
@@ -211,8 +209,8 @@ final class ServiceCollectorTests: XCTestCase {
211209
.registerIntoCollection(CustomService.self) { _ in CustomService(name: "service") }
212210
.inObjectScope(.transient)
213211

214-
let collection1 = container.resolveCollection(CustomService.self)
215-
let collection2 = container.resolveCollection(CustomService.self)
212+
let collection1 = container.resolver.resolveCollection(CustomService.self)
213+
let collection2 = container.resolver.resolveCollection(CustomService.self)
216214

217215
let instance1 = try XCTUnwrap(collection1.entries.first)
218216
let instance2 = try XCTUnwrap(collection2.entries.first)
@@ -223,7 +221,7 @@ final class ServiceCollectorTests: XCTestCase {
223221
@MainActor
224222
func test_registerIntoCollection_supportsContainerScopedObjects() throws {
225223
let swinjectContainer = Swinject.Container()
226-
let container = ContainerManager(swinjectContainer: swinjectContainer).register(Any.self)
224+
let container = ContainerManager(swinjectContainer: swinjectContainer).register(ParentResolver.self)
227225
container._unwrappedSwinjectContainer().addBehavior(ServiceCollector())
228226

229227
// Register a service with the `container` scope.
@@ -232,8 +230,8 @@ final class ServiceCollectorTests: XCTestCase {
232230
.registerIntoCollection(CustomService.self) { _ in CustomService(name: "service") }
233231
.inObjectScope(.container)
234232

235-
let collection1 = container.resolveCollection(CustomService.self)
236-
let collection2 = container.resolveCollection(CustomService.self)
233+
let collection1 = container.resolver.resolveCollection(CustomService.self)
234+
let collection2 = container.resolver.resolveCollection(CustomService.self)
237235

238236
let instance1 = try XCTUnwrap(collection1.entries.first)
239237
let instance2 = try XCTUnwrap(collection2.entries.first)
@@ -244,7 +242,7 @@ final class ServiceCollectorTests: XCTestCase {
244242
@MainActor
245243
func test_registerIntoCollection_supportsWeakScopedObjects() throws {
246244
let swinjectContainer = Swinject.Container()
247-
let container = ContainerManager(swinjectContainer: swinjectContainer).register(Any.self)
245+
let container = ContainerManager(swinjectContainer: swinjectContainer).register(ParentResolver.self)
248246
container._unwrappedSwinjectContainer().addBehavior(ServiceCollector())
249247

250248
// Register a service with the `weak` scope.
@@ -259,18 +257,18 @@ final class ServiceCollectorTests: XCTestCase {
259257
.inObjectScope(.weak)
260258

261259
// Resolve the initial instance
262-
var instance1: CustomService? = try XCTUnwrap(container.resolveCollection(CustomService.self).entries.first)
260+
var instance1: CustomService? = try XCTUnwrap(container.resolver.resolveCollection(CustomService.self).entries.first)
263261
XCTAssertEqual(factoryCallCount, 1)
264262

265263
// Resolving again shouldn't increase `factoryCallCount` since `instance1` is still retained.
266-
var instance2: CustomService? = try XCTUnwrap(container.resolveCollection(CustomService.self).entries.first)
264+
var instance2: CustomService? = try XCTUnwrap(container.resolver.resolveCollection(CustomService.self).entries.first)
267265
XCTAssertEqual(factoryCallCount, 1)
268266
XCTAssert(instance2 === instance1)
269267

270268
// Release our instances and resolve again. This time a new instance should be created.
271269
instance1 = nil
272270
instance2 = nil
273-
_ = container.resolveCollection(CustomService.self)
271+
_ = container.resolver.resolveCollection(CustomService.self)
274272
XCTAssertEqual(factoryCallCount, 2)
275273
}
276274

0 commit comments

Comments
 (0)