Skip to content

Commit

Permalink
Added WeakLazyInjected property wrapper for parent child relationships
Browse files Browse the repository at this point in the history
  • Loading branch information
hmlongco committed Jul 13, 2022
1 parent 89234ac commit 5eb558d
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Factory Changelog

### 1.2.5

* Added WeakLazyInjected property wrapper for parent child relationships

### 1.2.4

* Recursive lock required in scope resolution
Expand Down
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,37 @@ Next, note that Factory is *thread-safe.* Registrations and resolutions lock and

And finally, note that calling register also *removes any cached dependency from its associated scope.* This ensures that any new dependency injection request performed from that point on will always get the most recently defined instance of an object.

## Lazy and Weak Injections
Factory also has `LazyInjected` and `WeakLazyInjected` property wrappers. Use `LazyInjected` when you want to defer construction of some class until it's actually needed. Here the child `service` won't be instantiated until the `test` function is called.
```
class ServicesP {
@LazyInjected(Container.servicesC) var service
let name = "Parent"
init() {}
func test() -> String? {
service.name
}
}
```
And `WeakLazyInjected` is useful when building parent/child relationships and you want to avoid retain cycles back to the parent class. It's also lazy since otherwise you'd have a cyclic dependency between the parent and the child. (P needs C which needs P which needs C which...)'
```swift
class ServicesC {
@WeakLazyInjected(Container.servicesP) var service: ServicesP?
init() {}
let name = "Child"
func test() -> String? {
service?.name
}
}
```
And the factories. Note the shared scopes so references can be kept and maintained for the parent/child relationships.
```swift
extension Container {
static var servicesP = Factory(scope: .shared) { ServicesP() }
static var servicesC = Factory(scope: .shared) { ServicesC() }
}
```

## Custom Containers

In a large project you might want to segregate factories into additional, smaller containers.
Expand Down
27 changes: 24 additions & 3 deletions Sources/Factory/Factory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -306,15 +306,36 @@ extension SharedContainer.Scope {
@propertyWrapper public struct LazyInjected<T> {
private var factory: Factory<T>
private var dependency: T!
private var injectionNeeded = true
private var initialize = true
public init(_ factory: Factory<T>) {
self.factory = factory
}
public var wrappedValue: T {
mutating get {
if injectionNeeded {
if initialize {
dependency = factory()
injectionNeeded = false
initialize = false
}
return dependency
}
mutating set {
dependency = newValue
}
}
}

@propertyWrapper public struct WeakLazyInjected<T:AnyObject> {
private var factory: Factory<T>
private weak var dependency: T?
private var initialize = true
public init(_ factory: Factory<T>) {
self.factory = factory
}
public var wrappedValue: T? {
mutating get {
if initialize {
dependency = factory()
initialize = false
}
return dependency
}
Expand Down
33 changes: 33 additions & 0 deletions Tests/FactoryTests/FactoryInjectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,30 @@ class Services5 {
init() {}
}

class ServicesP {
@LazyInjected(Container.servicesC) var service
let name = "Parent"
init() {}
func test() -> String? {
service.name
}
}

class ServicesC {
@WeakLazyInjected(Container.servicesP) var service: ServicesP?
init() {}
let name = "Child"
func test() -> String? {
service?.name
}
}

extension Container {
fileprivate static var servicesP = Factory(scope: .shared) { ServicesP() }
fileprivate static var servicesC = Factory(scope: .shared) { ServicesC() }
}


final class FactoryInjectionTests: XCTestCase {

override func setUp() {
Expand Down Expand Up @@ -50,4 +74,13 @@ final class FactoryInjectionTests: XCTestCase {
XCTAssertTrue(services.service?.text() == "MyService")
}

func testWeakLazyInjection() throws {
var parent: ServicesP? = Container.servicesP()
let child = Container.servicesC()
XCTAssertTrue(parent?.test() == "Child")
XCTAssertTrue(child.test() == "Parent")
parent = nil
XCTAssertNil(child.test())
}

}

0 comments on commit 5eb558d

Please sign in to comment.