Skip to content

Commit 07d4c69

Browse files
committed
Fixed problems with protocol caching
1 parent 821c4a4 commit 07d4c69

File tree

6 files changed

+129
-16
lines changed

6 files changed

+129
-16
lines changed

CHANGELOG

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Factory Changelog
22

3+
### 1.2.6
4+
5+
* Fixed shared caching issue with non-optional protocol types
6+
* Fixed type problem with WeakLazyInjected and protocol types
7+
38
### 1.2.5
49

510
* Added WeakLazyInjected property wrapper for parent child relationships

Sources/Factory/Factory.swift

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -194,13 +194,15 @@ open class SharedContainer {
194194
fileprivate func resolve<T>(id: UUID, factory: () -> T) -> T {
195195
defer { lock.unlock() }
196196
lock.lock()
197-
if let box = cache[id], let instance = box.instance as? T {
198-
if let optional = instance as? OptionalProtocol {
199-
if optional.hasWrappedValue {
197+
if let box = cache[id] {
198+
if let instance = box.instance as? T {
199+
if let optional = instance as? OptionalProtocol {
200+
if optional.hasWrappedValue {
201+
return instance
202+
}
203+
} else {
200204
return instance
201205
}
202-
} else {
203-
return instance
204206
}
205207
}
206208
let instance: T = factory()
@@ -261,7 +263,7 @@ extension SharedContainer.Scope {
261263
if let unwrapped = optional.wrappedValue, type(of: unwrapped) is AnyObject.Type {
262264
return WeakBox(boxed: unwrapped as AnyObject)
263265
}
264-
} else if type(of: instance) is AnyObject.Type {
266+
} else {
265267
return WeakBox(boxed: instance as AnyObject)
266268
}
267269
return nil
@@ -324,23 +326,23 @@ extension SharedContainer.Scope {
324326
}
325327
}
326328

327-
@propertyWrapper public struct WeakLazyInjected<T:AnyObject> {
329+
@propertyWrapper public struct WeakLazyInjected<T> {
328330
private var factory: Factory<T>
329-
private weak var dependency: T?
331+
private weak var dependency: AnyObject?
330332
private var initialize = true
331333
public init(_ factory: Factory<T>) {
332334
self.factory = factory
333335
}
334336
public var wrappedValue: T? {
335337
mutating get {
336338
if initialize {
337-
dependency = factory()
339+
dependency = factory() as AnyObject
338340
initialize = false
339341
}
340-
return dependency
342+
return dependency as? T
341343
}
342344
mutating set {
343-
dependency = newValue
345+
dependency = newValue as AnyObject
344346
}
345347
}
346348
}

Tests/FactoryTests/FactoryDefectTests.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,22 @@ final class FactoryDefectTests: XCTestCase {
6161
XCTAssertTrue(text1 == text2)
6262
}
6363

64+
// Shared scope caching feiled when caching a non-optional protocol
65+
func testProtocolSharedScope() throws {
66+
var service1: MyServiceType? = Container.sharedExplicitProtocol()
67+
var service2: MyServiceType? = Container.sharedExplicitProtocol()
68+
XCTAssertNotNil(service1)
69+
XCTAssertNotNil(service2)
70+
// Shared cached item ids should match
71+
XCTAssertTrue(service1?.id == service2?.id)
72+
service1 = nil
73+
service2 = nil
74+
let service3: MyServiceType? = Container.sharedExplicitProtocol()
75+
XCTAssertNotNil(service3)
76+
// Shared instance should have released so new and old ids should not match
77+
XCTAssertTrue(service2?.id != service3?.id)
78+
}
79+
6480
}
6581

6682
fileprivate class TestLazyInjectionOccursOnce {

Tests/FactoryTests/FactoryInjectionTests.swift

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,46 @@ extension Container {
4141
fileprivate static var servicesC = Factory(scope: .shared) { ServicesC() }
4242
}
4343

44+
protocol ProtocolP: AnyObject {
45+
var name: String { get }
46+
func test() -> String?
47+
}
48+
49+
class ProtocolClassP: ProtocolP {
50+
let child = Container.protocolC()
51+
let name = "Parent"
52+
init() {}
53+
func test() -> String? {
54+
child.name
55+
}
56+
}
57+
58+
protocol ProtocolC: AnyObject {
59+
var parent: ProtocolP? { get set }
60+
var name: String { get }
61+
func test() -> String?
62+
}
63+
64+
class ProtocolClassC: ProtocolC {
65+
weak var parent: ProtocolP?
66+
init() {}
67+
let name = "Child"
68+
func test() -> String? {
69+
parent?.name
70+
}
71+
}
72+
73+
extension Container {
74+
fileprivate static var protocolP = Factory<ProtocolP> (scope: .shared) {
75+
let p = ProtocolClassP()
76+
p.child.parent = p
77+
return p
78+
}
79+
fileprivate static var protocolC = Factory<ProtocolC> (scope: .shared) {
80+
ProtocolClassC()
81+
}
82+
}
83+
4484

4585
final class FactoryInjectionTests: XCTestCase {
4686

@@ -83,4 +123,13 @@ final class FactoryInjectionTests: XCTestCase {
83123
XCTAssertNil(child.test())
84124
}
85125

126+
func testWeakLazyInjectionProtocol() throws {
127+
var parent: ProtocolP? = Container.protocolP()
128+
let child: ProtocolC? = Container.protocolC()
129+
XCTAssertTrue(parent?.test() == "Child")
130+
XCTAssertTrue(child?.test() == "Parent")
131+
parent = nil
132+
XCTAssertNil(child?.test())
133+
}
134+
86135
}

Tests/FactoryTests/FactoryScopeTests.swift

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,46 @@ final class FactoryScopeTests: XCTestCase {
5656
XCTAssertTrue(service2?.id != service3?.id)
5757
}
5858

59+
func testExplicitProtocolSharedScope() throws {
60+
var service1: MyServiceType? = Container.sharedExplicitProtocol()
61+
var service2: MyServiceType? = Container.sharedExplicitProtocol()
62+
XCTAssertNotNil(service1)
63+
XCTAssertNotNil(service2)
64+
// Shared cached item ids should match
65+
XCTAssertTrue(service1?.id == service2?.id)
66+
service1 = nil
67+
service2 = nil
68+
let service3: MyServiceType? = Container.sharedExplicitProtocol()
69+
XCTAssertNotNil(service3)
70+
// Shared instance should have released so new and old ids should not match
71+
XCTAssertTrue(service2?.id != service3?.id)
72+
}
73+
74+
func testInferredProtocolSharedScope() throws {
75+
var service1: MyServiceType? = Container.sharedInferredProtocol()
76+
var service2: MyServiceType? = Container.sharedInferredProtocol()
77+
XCTAssertNotNil(service1)
78+
XCTAssertNotNil(service2)
79+
// Shared cached item ids should match
80+
XCTAssertTrue(service1?.id == service2?.id)
81+
service1 = nil
82+
service2 = nil
83+
let service3: MyServiceType? = Container.sharedInferredProtocol()
84+
XCTAssertNotNil(service3)
85+
// Shared instance should have released so new and old ids should not match
86+
XCTAssertTrue(service2?.id != service3?.id)
87+
}
88+
5989
func testOptionalSharedScope() throws {
60-
var service1: MyServiceType? = Container.optionalSharedService()
61-
var service2: MyServiceType? = Container.optionalSharedService()
90+
var service1: MyServiceType? = Container.sharedOptionalProtocol()
91+
var service2: MyServiceType? = Container.sharedOptionalProtocol()
6292
XCTAssertNotNil(service1)
6393
XCTAssertNotNil(service2)
6494
// Shared cached item ids should match
6595
XCTAssertTrue(service1?.id == service2?.id)
6696
service1 = nil
6797
service2 = nil
68-
let service3: MyServiceType? = Container.optionalSharedService()
98+
let service3: MyServiceType? = Container.sharedOptionalProtocol()
6999
XCTAssertNotNil(service3)
70100
// Shared instance should have released so new and old ids should not match
71101
XCTAssertTrue(service2?.id != service3?.id)

Tests/FactoryTests/MockServices.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,19 +80,30 @@ class ParameterService: MyServiceType {
8080
extension Container {
8181
static let myServiceType = Factory<MyServiceType> { MyService() }
8282
static let myServiceType2 = Factory<MyServiceType> { MyService() }
83+
8384
static let mockService = Factory { MockService() }
85+
8486
static let cachedService = Factory(scope: .cached) { MyService() }
87+
8588
static let sharedService = Factory(scope: .shared) { MyService() }
86-
static let singletonService = Factory(scope: .singleton) { MyService() }
89+
static let sharedExplicitProtocol = Factory<MyServiceType>(scope: .shared) { MyService() }
90+
static let sharedInferredProtocol = Factory(scope: .shared) { MyService() as MyServiceType }
91+
static let sharedOptionalProtocol = Factory<MyServiceType?>(scope: .shared) { MyService() }
92+
8793
static let optionalService = Factory<MyServiceType?> { MyService() }
88-
static let optionalSharedService = Factory<MyServiceType?>(scope: .shared) { MyService() }
8994
static let optionalValueService = Factory<MyServiceType?> { ValueService() }
95+
96+
static let singletonService = Factory(scope: .singleton) { MyService() }
97+
9098
static let nilSService = Factory<MyServiceType?> { nil }
9199
static let nilCachedService = Factory<MyServiceType?>(scope: .cached) { nil }
92100
static let nilSharedService = Factory<MyServiceType?>(scope: .shared) { nil }
101+
93102
static let sessionService = Factory(scope: .session) { MyService() }
103+
94104
static let valueService = Factory(scope: .cached) { ValueService() }
95105
static let sharedValueService = Factory(scope: .shared) { ValueService() }
106+
96107
static let promisedService = Factory<MyServiceType?> { nil }
97108
}
98109

0 commit comments

Comments
 (0)