-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from Tavernari/default-injectable-value
Enhanced Dependency Injection Functionality and Documentation
- Loading branch information
Showing
6 changed files
with
184 additions
and
48 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 |
---|---|---|
@@ -1,51 +1,100 @@ | ||
// | ||
// Container.swift | ||
// | ||
// | ||
// Created by Victor C Tavernari on 07/08/21. | ||
// | ||
|
||
import Foundation | ||
|
||
/// `Container`: A singleton class to manage dependency injections. | ||
/// | ||
/// This class provides a shared instance to manage dependencies across the application. | ||
/// It allows for registering and retrieving dependencies via a dictionary. | ||
public class Container: Injectable { | ||
|
||
/// The shared instance of `Container`. | ||
/// | ||
/// Use this static property to access the same instance of `Container` throughout the application. | ||
public static var standard = Container() | ||
|
||
/// A dictionary holding the dependencies. | ||
/// | ||
/// The dependencies are stored as key-value pairs where the key | ||
/// is any hashable object and the value is the dependency. | ||
public var dependencies: [AnyHashable: Any] = [:] | ||
|
||
/// Creates a new instance of `Container`. | ||
/// | ||
/// This initializer is public and required as per the `Injectable` protocol. | ||
required public init() {} | ||
} | ||
|
||
/// A property wrapper for injecting dependencies. | ||
/// | ||
/// This struct wraps a property and injects a dependency into it. | ||
/// If the dependency cannot be resolved and no default value is provided, | ||
/// it will crash the application. | ||
@propertyWrapper public struct Injected<Value> { | ||
|
||
/// Error types related to dependency injection. | ||
enum Error: Swift.Error { | ||
case couldNotResolveAndDefaultIsNil | ||
} | ||
|
||
/// Returns the standard container used for resolving dependencies. | ||
public static func container() -> Injectable { Container.standard } | ||
|
||
private let identifier: InjectIdentifier<Value> | ||
private let container: Resolvable | ||
public init(_ identifier: InjectIdentifier<Value>? = nil, container: Resolvable? = nil) { | ||
private let `default`: Value? | ||
|
||
/// Creates a new `Injected` instance. | ||
/// | ||
/// - Parameters: | ||
/// - identifier: The identifier used to resolve the dependency. Defaults to the type of `Value`. | ||
/// - container: The container used for resolving the dependency. Defaults to `Container.standard`. | ||
/// - default: An optional default value to use if the dependency cannot be resolved. | ||
public init(_ identifier: InjectIdentifier<Value>? = nil, container: Resolvable? = nil, `default`: Value? = nil) { | ||
self.identifier = identifier ?? .by(type: Value.self) | ||
self.container = container ?? Self.container() | ||
self.default = `default` | ||
} | ||
|
||
/// The resolved value of the dependency. | ||
/// | ||
/// This property lazily resolves the dependency. | ||
/// If the dependency cannot be resolved, it will use the provided default value. | ||
/// If both fail, the application will crash. | ||
public lazy var wrappedValue: Value = { | ||
do { | ||
|
||
return try container.resolve(identifier) | ||
|
||
} catch { fatalError( error.localizedDescription ) } | ||
if let value = try? container.resolve(identifier) { | ||
return value | ||
} | ||
|
||
if let `default` { | ||
return `default` | ||
} | ||
|
||
fatalError("Could not resolve with \(identifier) and default is nil") | ||
}() | ||
} | ||
|
||
/// A property wrapper for safely injecting dependencies. | ||
/// | ||
/// This struct wraps a property and injects an optional dependency into it. | ||
/// If the dependency cannot be resolved, the property will be nil. | ||
@propertyWrapper public struct InjectedSafe<Value> { | ||
|
||
/// Returns the standard container used for resolving dependencies. | ||
public static func container() -> Injectable { Container.standard } | ||
|
||
private let identifier: InjectIdentifier<Value> | ||
private let container: Resolvable | ||
|
||
/// Creates a new `InjectedSafe` instance. | ||
/// | ||
/// - Parameters: | ||
/// - identifier: The identifier used to resolve the dependency. Defaults to the type of `Value`. | ||
/// - container: The container used for resolving the dependency. Defaults to `Container.standard`. | ||
public init(_ identifier: InjectIdentifier<Value>? = nil, container: Resolvable? = nil) { | ||
self.identifier = identifier ?? .by(type: Value.self) | ||
self.container = container ?? Self.container() | ||
} | ||
|
||
/// The optionally resolved value of the dependency. | ||
/// | ||
/// This property lazily tries to resolve the dependency. | ||
/// If the dependency cannot be resolved, the property will be nil. | ||
public lazy var wrappedValue: Value? = try? container.resolve(identifier) | ||
} |
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 |
---|---|---|
@@ -1,92 +1,139 @@ | ||
import Foundation | ||
|
||
/// A protocol defining the ability to resolve dependencies. | ||
public protocol Resolvable { | ||
|
||
/// Resolves a dependency based on an identifier. | ||
/// | ||
/// - Parameter identifier: The identifier for the dependency to be resolved. | ||
/// - Throws: An error if the dependency cannot be resolved. | ||
/// - Returns: The resolved dependency of the given type `Value`. | ||
func resolve<Value>(_ identifier: InjectIdentifier<Value>) throws -> Value | ||
} | ||
|
||
/// An enumeration representing errors | ||
/// that can occur during the resolution of dependencies. | ||
public enum ResolvableError: Error { | ||
|
||
/// Indicates that a dependency could not be found. | ||
/// | ||
/// - Parameters: | ||
/// - type: The type of the dependency that was not found. | ||
/// - key: An optional key associated with the dependency. | ||
case dependencyNotFound(Any.Type?, String?) | ||
} | ||
|
||
/// Extension to make `ResolvableError` conform | ||
/// to `LocalizedError`, providing a localized description of the error. | ||
extension ResolvableError: LocalizedError { | ||
|
||
/// A localized description of the error. | ||
public var errorDescription: String? { | ||
switch self { | ||
case let .dependencyNotFound(type, key): | ||
|
||
var message = "Could not find dependency for " | ||
|
||
if let type = type { | ||
message += "type: \(type) " | ||
} else if let key = key { | ||
message += "key: \(key)" | ||
} | ||
|
||
return message | ||
} | ||
} | ||
} | ||
|
||
/// A protocol representing an object that can inject dependencies. | ||
/// | ||
/// Conforming types can register, remove, and resolve dependencies. | ||
public protocol Injectable: Resolvable, AnyObject { | ||
|
||
/// Initializes a new instance. | ||
init() | ||
|
||
/// A dictionary to hold dependencies. | ||
var dependencies: [AnyHashable: Any] { get set } | ||
|
||
/// Registers a dependency with an identifier. | ||
/// | ||
/// - Parameters: | ||
/// - identifier: The identifier for the dependency. | ||
/// - resolve: A closure that resolves the dependency. | ||
func register<Value>(_ identifier: InjectIdentifier<Value>, _ resolve: (Resolvable) throws -> Value) | ||
|
||
/// Removes a dependency associated with an identifier. | ||
/// | ||
/// - Parameter identifier: The identifier for the dependency to be removed. | ||
func remove<Value>(_ identifier: InjectIdentifier<Value>) | ||
} | ||
|
||
/// Default implementations for the `Injectable` protocol. | ||
public extension Injectable { | ||
|
||
|
||
/// Registers a dependency. | ||
/// | ||
/// - Parameters: | ||
/// - identifier: The identifier for the dependency. | ||
/// - resolve: A closure that resolves the dependency. | ||
func register<Value>(_ identifier: InjectIdentifier<Value>, _ resolve: (Resolvable) throws -> Value) { | ||
|
||
do { | ||
|
||
self.dependencies[identifier] = try resolve( self ) | ||
|
||
self.dependencies[identifier] = try resolve(self) | ||
} catch { | ||
|
||
assertionFailure(error.localizedDescription) | ||
} | ||
} | ||
|
||
/// Convenience method to register a dependency using type and optional key. | ||
/// | ||
/// - Parameters: | ||
/// - type: The type of the dependency. | ||
/// - key: An optional key for the dependency. | ||
/// - resolve: A closure that resolves the dependency. | ||
func register<Value>(type: Value.Type? = nil, key: String? = nil, _ resolve: (Resolvable) throws -> Value) { | ||
|
||
self.register(.by(type: type, key: key), resolve) | ||
} | ||
|
||
|
||
/// Removes a dependency associated with an identifier. | ||
/// | ||
/// - Parameter identifier: The identifier for the dependency to be removed. | ||
func remove<Value>(_ identifier: InjectIdentifier<Value>) { | ||
|
||
self.dependencies.removeValue(forKey: identifier) | ||
} | ||
|
||
/// Convenience method to remove a dependency using type and optional key. | ||
/// | ||
/// - Parameters: | ||
/// - type: The type of the dependency. | ||
/// - key: An optional key for the dependency. | ||
func remove<Value>(type: Value.Type? = nil, key: String? = nil) { | ||
|
||
let identifier = InjectIdentifier.by(type: type, key: key) | ||
self.dependencies.removeValue(forKey: identifier) | ||
} | ||
|
||
/// Removes all dependencies from the container. | ||
func removeAllDependencies() { | ||
|
||
self.dependencies.removeAll() | ||
} | ||
|
||
/// Resolves a dependency based on an identifier. | ||
/// | ||
/// - Parameter identifier: The identifier for the dependency to be resolved. | ||
/// - Throws: `ResolvableError.dependencyNotFound` if the dependency cannot be found. | ||
/// - Returns: The resolved dependency of the given type `Value`. | ||
func resolve<Value>(_ identifier: InjectIdentifier<Value>) throws -> Value { | ||
|
||
guard let dependency = dependencies[identifier] as? Value else { | ||
|
||
throw ResolvableError.dependencyNotFound(identifier.type, identifier.key) | ||
} | ||
|
||
return dependency | ||
} | ||
|
||
/// Convenience method to resolve a dependency using type and optional key. | ||
/// | ||
/// - Parameters: | ||
/// - type: The type of the dependency. | ||
/// - key: An optional key for the dependency. | ||
/// - Throws: `ResolvableError.dependencyNotFound` if the dependency cannot be found. | ||
/// - Returns: The resolved dependency of the given type `Value`. | ||
func resolve<Value>(type: Value.Type? = nil, key: String? = nil) throws -> Value { | ||
|
||
try self.resolve(.by(type: type, key: key)) | ||
} | ||
} |
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 |
---|---|---|
@@ -1,43 +1,59 @@ | ||
// | ||
// InjectIdentifier.swift | ||
// | ||
// | ||
// Created by Victor C Tavernari on 07/08/21. | ||
// | ||
|
||
import Foundation | ||
|
||
/// A structure used to uniquely identify dependencies for injection. | ||
public struct InjectIdentifier<Value> { | ||
|
||
/// The type of the value to be injected. | ||
private(set) var type: Value.Type? = nil | ||
|
||
/// An optional key to further distinguish dependencies of the same type. | ||
private(set) var key: String? = nil | ||
|
||
/// Private initializer to create an `InjectIdentifier` instance. | ||
/// | ||
/// - Parameters: | ||
/// - type: The type of the value to be injected. | ||
/// - key: An optional key to further distinguish dependencies. | ||
private init(type: Value.Type? = nil, key: String? = nil) { | ||
|
||
self.type = type | ||
self.key = key | ||
} | ||
} | ||
|
||
/// Extension to make `InjectIdentifier` conform to `Hashable`. | ||
extension InjectIdentifier: Hashable { | ||
|
||
public static func == (lhs: InjectIdentifier, rhs: InjectIdentifier) -> Bool { lhs.hashValue == rhs.hashValue } | ||
/// Determines equality between two `InjectIdentifier` instances. | ||
/// | ||
/// - Parameters: | ||
/// - lhs: The left-hand side `InjectIdentifier` instance. | ||
/// - rhs: The right-hand side `InjectIdentifier` instance. | ||
/// - Returns: A Boolean value indicating whether the two instances are equal. | ||
public static func == (lhs: InjectIdentifier, rhs: InjectIdentifier) -> Bool { | ||
lhs.hashValue == rhs.hashValue | ||
} | ||
|
||
/// Hashes the essential components of this value by feeding them into the given hasher. | ||
/// | ||
/// - Parameter hasher: The hasher to use when combining the components of this instance. | ||
public func hash(into hasher: inout Hasher) { | ||
|
||
hasher.combine(self.key) | ||
|
||
if let type = self.type { | ||
|
||
hasher.combine(ObjectIdentifier(type)) | ||
} | ||
} | ||
} | ||
|
||
/// Public extension to provide a convenient way to create an `InjectIdentifier`. | ||
public extension InjectIdentifier { | ||
|
||
/// Creates an `InjectIdentifier` instance. | ||
/// | ||
/// - Parameters: | ||
/// - type: The type of the value to be injected. | ||
/// - key: An optional key to further distinguish dependencies. | ||
/// - Returns: An `InjectIdentifier` instance. | ||
static func by(type: Value.Type? = nil, key: String? = nil ) -> InjectIdentifier { | ||
|
||
return .init(type: type, key: key) | ||
} | ||
} |
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