-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* added syncronization queue for Injected instances * updated access synchronization in Injected * updated Injection macro codegen * added simple test for injection * updated macro codegen * Added docs for 2.1 * minor doc fix * conditional import to run on CI * Bumped swift to 5.10 * refactoring * refactoring * refactoring * splitter store injection and dependency inejction * updated tests * minor refactoring * added migration guide link * minor docs change and deprecation * minor fix
- Loading branch information
1 parent
1a7ee57
commit 8f37de5
Showing
26 changed files
with
520 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Sergey Kazakov on 17/09/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct Dependencies: Sendable, DependencyContainer { | ||
public init() { | ||
|
||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Sergey Kazakov on 17/09/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
@propertyWrapper | ||
public struct Dependency<T> { | ||
private let keyPath: WritableKeyPath<Dependencies, T> | ||
|
||
public var wrappedValue: T { | ||
get { Dependencies[keyPath] } | ||
} | ||
|
||
public init(_ keyPath: WritableKeyPath<Dependencies, T>) { | ||
self.keyPath = keyPath | ||
} | ||
|
||
public static subscript(_ keyPath: WritableKeyPath<Dependencies, T>) -> T { | ||
get { | ||
Dependencies[keyPath] | ||
} | ||
} | ||
} | ||
|
||
|
||
protocol Service { | ||
|
||
} | ||
|
||
struct ServiceImp: Service { | ||
|
||
} | ||
|
||
extension Dependencies { | ||
@DependencyEntry var intValue = 1 | ||
|
||
@DependencyEntry var uuid = { UUID() } | ||
@DependencyEntry var now = { Date() } | ||
|
||
} | ||
|
||
class Foo { | ||
@Dependency(\.intValue) var value | ||
|
||
var uuidValue = Dependency[\.uuid] | ||
|
||
func ffofofo() { | ||
Dependencies[\.now] = { .distantPast } | ||
} | ||
} | ||
|
||
|
42 changes: 42 additions & 0 deletions
42
Sources/Puredux/DependencyInjection/DependencyContainer.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Sergey Kazakov on 25/08/2024. | ||
// | ||
|
||
import Foundation | ||
import Dispatch | ||
|
||
public protocol DependencyContainer: Sendable { | ||
init() | ||
} | ||
|
||
/** Provides access to injected dependencies. */ | ||
extension DependencyContainer { | ||
|
||
/** A static subscript for updating the `currentValue` of `DependencyKey` instances. */ | ||
public subscript<Key>(key: Key.Type) -> Key.Value where Key: DependencyKey, Key.Value: Sendable { | ||
get { | ||
DispatchQueue.di.sync { key.currentValue } | ||
} | ||
set { | ||
DispatchQueue.di.async(flags: .barrier) { key.currentValue = newValue } | ||
} | ||
} | ||
|
||
/** A static subscript accessor for updating and references dependencies directly. */ | ||
public static subscript<T>(_ keyPath: WritableKeyPath<Self, T>) -> T { | ||
get { | ||
Self()[keyPath: keyPath] | ||
} | ||
set { | ||
var instance = Self() | ||
instance[keyPath: keyPath] = newValue | ||
} | ||
} | ||
} | ||
|
||
fileprivate extension DispatchQueue { | ||
static let di = DispatchQueue(label: "com.puredux.dependencies", attributes: .concurrent) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Sergey Kazakov on 18/09/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
/** | ||
A protocol that defines a key used for dependency injection. | ||
|
||
Types conforming to `DependencyKey` provide a mechanism to inject dependencies by associating a specific type of value (`Value`) . | ||
*/ | ||
public protocol DependencyKey { | ||
/** The associated type representing the type of the dependency injection key's value. */ | ||
associatedtype Value | ||
|
||
/** The default value for the dependency injection key. */ | ||
static var currentValue: Self.Value { get set } | ||
} | ||
|
||
/** | ||
A protocol that defines a key used for store injection. | ||
|
||
Types conforming to `StoreInjectionKey` provide a mechanism to inject . | ||
*/ | ||
public protocol StoreInjectionKey: DependencyKey where Value: Store { | ||
/** The associated type representing the type of the dependency injection key's value. */ | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
// | ||
// File.swift | ||
// | ||
// | ||
// Created by Sergey Kazakov on 17/09/2024. | ||
// | ||
|
||
import Foundation | ||
|
||
@available(*, deprecated, message: "use SharedStores instead", renamed: "SharedStores") | ||
public typealias Injected = SharedStores | ||
|
||
public struct SharedStores: Sendable, DependencyContainer { | ||
public init() { | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
117 changes: 117 additions & 0 deletions
117
Sources/Puredux/Documentation.docc/Articles/DependencyInjection.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# Dependency Injection | ||
|
||
Dealing with dependencies in Puredux | ||
|
||
## Overview | ||
|
||
Dependencies in an application refer to the types and functions that interact with external systems or components beyond your control. | ||
|
||
Puredux is designed to offload all "side effects" to the application's "shell," allowing the core logic to remain as pure and isolated as possible. | ||
|
||
However, in practice, maintaining a completely pure core can still be challenging. This includes handling things like Date, UUID, Locale, feature flags, and configuration. | ||
|
||
In such cases, Dependency Injection becomes a powerful and convenient tool for managing external interactions while preserving the core's integrity. | ||
|
||
## Dependencies Injection in Puredux | ||
|
||
Puredux splits dependencies into two categories: | ||
|
||
- Store Injection | ||
- Dependency Injection | ||
|
||
Although both are essentially dependencies, they are handled separately because they serve different purposes, and we want to ensure they remain distinct. | ||
|
||
**Store Injection** is used to conveniently obtain store instances in the UI layer of the application. | ||
|
||
**Dependency Injection** is used inside the store's reducers to power the application's core logic. | ||
|
||
### Stores Injection | ||
|
||
Use `@StoreEntry` in the `SharedStores` extension to inject the store instance: | ||
|
||
|
||
```swift | ||
extension SharedStores { | ||
@StoreEntry var root = StateStore<AppRootState, Action>(....) | ||
} | ||
``` | ||
|
||
The `@StoreOf` property wrapper can be used to obtain the injected store instance: | ||
|
||
|
||
```swift | ||
struct MyView: View { | ||
@State @StoreOf(\.root) | ||
var store: StateStore<AppRootState, Action> | ||
|
||
var body: some View { | ||
// ... | ||
} | ||
} | ||
``` | ||
|
||
Alternatively, you can access it directly: | ||
|
||
```swift | ||
let store = StoreOf[\.root] | ||
``` | ||
|
||
If you need to bypass the store's entry point and set the store dependency directly, you can do so via: | ||
|
||
```swift | ||
|
||
SharedStores[\.root] = StateStore<AppRootState, Action>(...) | ||
|
||
``` | ||
|
||
### Dependency Injection | ||
|
||
Use `@DependencyEntry` in the `Dependencies` extension to inject the dependency instance: | ||
|
||
```swift | ||
extension Dependencies { | ||
@DependencyEntry var now = { Date() } | ||
} | ||
``` | ||
|
||
Then it can be used in the app reducer: | ||
|
||
```swift | ||
struct AppState { | ||
private var currentTime: Date? | ||
|
||
mutating func reduce(_ action: Action) { | ||
switch action { | ||
case let action as UpdateTime: | ||
let now = Dependency[\.now] | ||
currentTime = now() | ||
default: | ||
break | ||
} | ||
} | ||
} | ||
|
||
``` | ||
|
||
If you need to bypass the dependency's entry point and set it directly, you can do so via: | ||
|
||
|
||
```swift | ||
|
||
Dependencies[\.now] = { .distantPast } | ||
|
||
``` | ||
|
||
## Discussion | ||
|
||
By implementing a clear separation of concerns between store injection and dependency injection, we gain significant control over their lifecycle and usage: | ||
|
||
- Clear Distinction Between Store Injection and Dependency Injection: This separation allows for the swift identification and correction of any misuse or misconfiguration of store hierarchy. | ||
|
||
- `StoreEntry` and `DependencyEntry`: These constructs help to clearly define and locate the entry points for both stores and dependencies. This visibility simplifies debugging and maintenance, as developers can quickly trace where and how dependencies and stores are being injected into the application. | ||
|
||
- Read-Only Access with `Dependency` and `StoreOf`: These mechanisms provide controlled, read-only access to the dependency injection containers. By restricting modification capabilities, they help maintain the integrity of the state and dependencies throughout the app, reducing the risk of unintended misuse | ||
|
||
- Specialization of `StoreOf`: This property wrapper is specifically designed to support only `StateStore` types, which are responsible for owning the state. `@StoreEntry` only supports values of `any Store` type. | ||
|
||
- Controlled Access with `Dependencies` and `SharedStores`: These tools offer read and write access to the dependency injection containers, but within a confined scope of the application. This limitation ensures that modifications to dependencies and stores are kept localized, reducing the risk of unintended changes. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.