Skip to content

feat: Containers feature #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,16 @@ Optimised to work well with SwiftUI Views.
- [x] Injection by name
- [x] Injection with Arguments
- [x] Lazy init (with @LazyInjectService)

## Roadmap
- [ ] Optionals
- [ ] Multiple containers
- [.] Multiple containers

## Requirements
- Swift 5.6 +
- Swift 5.7 +
- SPM

## Setup

TODO: Containers doc

Early on in the lifecycle of your app, you want to write something like this :

```swift
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Foundation
/// Something minimalist that can resolve a concrete type
///
/// Servicies are kept alive for the duration of the app's life
public protocol SimpleResolvable {
public protocol Resolvable {
/// The main solver funtion, tries to fetch an existing object or apply a factory if availlable
/// - Parameters:
/// - type: the wanted type
Expand All @@ -30,11 +30,11 @@ public protocol SimpleResolvable {
func resolve<Service>(type: Service.Type,
forCustomTypeIdentifier customIdentifier: String?,
factoryParameters: [String: Any]?,
resolver: SimpleResolvable) throws -> Service
resolver: Resolvable) throws -> Service
}

/// Something that stores a factory for a given type
public protocol SimpleStorable {
public protocol FactoryStorable {
/// Store a factory closure for a given type
///
/// You will virtualy never call this directly
Expand All @@ -48,7 +48,7 @@ public protocol SimpleStorable {

/// A minimalist DI solution
/// Once initiated, stores types as long as the app lives
public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebugStringConvertible {
public final class Container: Resolvable, FactoryStorable, CustomDebugStringConvertible {
public var debugDescription: String {
var buffer: String!
queue.sync {
Expand All @@ -67,18 +67,20 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug
case typeMissmatch(expected: String, got: String)
}

/// One singleton to rule them all
public static let sharedResolver = SimpleResolver()

/// Factory collection
var factories = [String: Factoryable]()
var factories: [String: Factoryable]

/// Resolved object collection
var store = [String: Any]()

/// A serial queue for thread safety
private let queue = DispatchQueue(label: "com.infomaniakDI.resolver")

public init(factories: [String: any Factoryable] = [String: Factoryable]()) {
self.factories = factories
self.store = [String: Any]()
}

// MARK: SimpleStorable

public func store(factory: Factoryable,
Expand All @@ -96,7 +98,7 @@ public final class SimpleResolver: SimpleResolvable, SimpleStorable, CustomDebug
public func resolve<Service>(type: Service.Type,
forCustomTypeIdentifier customIdentifier: String?,
factoryParameters: [String: Any]? = nil,
resolver: SimpleResolvable) throws -> Service {
resolver: Resolvable) throws -> Service {
let serviceIdentifier = buildIdentifier(type: type, forIdentifier: customIdentifier)

// load form store
Expand Down
25 changes: 25 additions & 0 deletions Sources/InfomaniakDI/DependencyInjectionService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import Foundation

/// Entry point of the DI library
///
/// Allows to add and remove dynamically containers.
/// Also provides a standard, shared, singleton style, container.
public final class DependencyInjectionService {
/// Shared container of all singletons of an executable
public static let sharedContainer = Container()

public init() {}
}
6 changes: 3 additions & 3 deletions Sources/InfomaniakDI/Factory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import Foundation

/// Something that can build a type, given some extra parameters and a resolver for chained dependency
public typealias FactoryClosure = (_ parameters: [String: Any]?, _ resolver: SimpleResolvable) throws -> Any
public typealias FactoryClosure = (_ parameters: [String: Any]?, _ resolver: Resolvable) throws -> Any

/// Something that can build a type
public protocol Factoryable {
Expand All @@ -29,7 +29,7 @@ public protocol Factoryable {
/// - factoryParameters: Extra parameters that can be used to customize a type.
/// - resolver: A resolver for chained resolution
/// - Returns: Return something that can be casted as the `type` declared at init. Will throw otherwise.
func build(factoryParameters: [String: Any]?, resolver: SimpleResolvable) throws -> Any
func build(factoryParameters: [String: Any]?, resolver: Resolvable) throws -> Any

/// The registered type, prefer using a Protocol here. Great for testing.
var type: Any.Type { get }
Expand All @@ -49,7 +49,7 @@ public struct Factory: Factoryable, CustomDebugStringConvertible {
}

public func build(factoryParameters: [String: Any]? = nil,
resolver: SimpleResolvable = SimpleResolver.sharedResolver) throws -> Any {
resolver: Resolvable) throws -> Any {
try closure(factoryParameters, resolver)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,47 +15,47 @@ import Foundation

// MARK: - InjectService<Service>

/// A property wrapper that resolves shared objects when the host type is initialized.
@propertyWrapper public struct InjectService<Service>: CustomDebugStringConvertible, Equatable, Identifiable {
/// A property wrapper that resolves shared types when the host type is initialized.
@propertyWrapper public struct Inject<Injected>: CustomDebugStringConvertible, Equatable, Identifiable {
/// Identifiable
///
/// Something to link the identity of this property wrapper to the underlying Service type.
public let id = ObjectIdentifier(Service.self)
public let id = ObjectIdentifier(Injected.self)

/// Equatable
///
/// Two `InjectService` that points to the same `Service` Metatype are expected to be equal (for the sake of SwiftUI
/// Two `InjectService` that points to the same `Injected` Metatype are expected to be equal (for the sake of SwiftUI
/// correctness)
public static func == (lhs: InjectService<Service>, rhs: InjectService<Service>) -> Bool {
public static func == (lhs: Inject<Injected>, rhs: Inject<Injected>) -> Bool {
return lhs.id == rhs.id
}

public var debugDescription: String {
"""
<\(type(of: self))
wrapping type:'\(Service.self)'
wrapping type:'\(Injected.self)'
customTypeIdentifier:\(String(describing: customTypeIdentifier))
factoryParameters:\(String(describing: factoryParameters))
id:\(id)'>
"""
}

/// Store the resolved service
var service: Service!
var service: Injected!

public var container: SimpleResolvable
public var container: Resolvable
public var customTypeIdentifier: String?
public var factoryParameters: [String: Any]?

public init(customTypeIdentifier: String? = nil,
factoryParameters: [String: Any]? = nil,
container: SimpleResolvable = SimpleResolver.sharedResolver) {
container: Resolvable) {
self.customTypeIdentifier = customTypeIdentifier
self.factoryParameters = factoryParameters
self.container = container

do {
service = try container.resolve(type: Service.self,
service = try container.resolve(type: Injected.self,
forCustomTypeIdentifier: customTypeIdentifier,
factoryParameters: factoryParameters,
resolver: container)
Expand All @@ -64,7 +64,7 @@ import Foundation
}
}

public var wrappedValue: Service {
public var wrappedValue: Injected {
get {
service
}
Expand Down
77 changes: 77 additions & 0 deletions Sources/InfomaniakDI/PropertyWrappers/InjectService.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import Foundation

/// A property wrapper that resolves shared types when the host type is initialized.
@propertyWrapper public struct InjectService<Injected>: CustomDebugStringConvertible, Equatable, Identifiable {
/// Identifiable
///
/// Something to link the identity of this property wrapper to the underlying Service type.
public let id = ObjectIdentifier(Injected.self)

/// Equatable
///
/// Two `InjectService` that points to the same `Injected` Metatype are expected to be equal (for the sake of SwiftUI
/// correctness)
public static func == (lhs: InjectService<Injected>, rhs: InjectService<Injected>) -> Bool {
return lhs.id == rhs.id
}

public var debugDescription: String {
"""
<\(type(of: self))
wrapping type:'\(Injected.self)'
customTypeIdentifier:\(String(describing: customTypeIdentifier))
factoryParameters:\(String(describing: factoryParameters))
id:\(id)'>
"""
}

/// Store the resolved service
var service: Injected!

public var container: Resolvable
public var customTypeIdentifier: String?
public var factoryParameters: [String: Any]?

public init(customTypeIdentifier: String? = nil,
factoryParameters: [String: Any]? = nil) {
self.customTypeIdentifier = customTypeIdentifier
self.factoryParameters = factoryParameters
container = DependencyInjectionService.sharedContainer

do {
service = try container.resolve(type: Injected.self,
forCustomTypeIdentifier: customTypeIdentifier,
factoryParameters: factoryParameters,
resolver: container)
} catch {
fatalError("DI fatal error :\(error)")
}
}

public var wrappedValue: Injected {
get {
service
}
set {
fatalError("You are not expected to substitute resolved objects")
}
}

/// The property wrapper itself for debugging and testing
public var projectedValue: Self {
self
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,58 +13,58 @@

import Foundation

/// Inject a service at the first use of the property
@propertyWrapper public final class LazyInjectService<Service>: Equatable, Identifiable {
/// Inject a type at the first use of the property
@propertyWrapper public class LazyInject<Injected>: Equatable, Identifiable {
/// Identifiable
///
/// Something to link the identity of this property wrapper to the underlying Service type.
public let id = ObjectIdentifier(Service.self)
public let id = ObjectIdentifier(Injected.self)

/// Equatable
///
/// Two `LazyInjectService` that points to the same `Service` Metatype are expected to be equal (for the sake of SwiftUI
/// correctness)
public static func == (lhs: LazyInjectService<Service>, rhs: LazyInjectService<Service>) -> Bool {
public static func == (lhs: LazyInject<Injected>, rhs: LazyInject<Injected>) -> Bool {
return lhs.id == rhs.id
}

public var debugDescription: String {
"""
<\(type(of: self))
wrapping type:'\(Service.self)'
wrapping type:'\(Injected.self)'
customTypeIdentifier:\(String(describing: customTypeIdentifier))
factoryParameters:\(String(describing: factoryParameters))
id:\(id)'>
"""
}

/// Store the resolved service
var service: Service?
/// Store the instance of the resolved type
var resolvedInstance: Injected?

public var container: SimpleResolvable
public var container: Resolvable
public var customTypeIdentifier: String?
public var factoryParameters: [String: Any]?

public init(customTypeIdentifier: String? = nil,
factoryParameters: [String: Any]? = nil,
container: SimpleResolvable = SimpleResolver.sharedResolver) {
container: Resolvable) {
self.customTypeIdentifier = customTypeIdentifier
self.factoryParameters = factoryParameters
self.container = container
}

public var wrappedValue: Service {
public var wrappedValue: Injected {
get {
if let service {
return service
if let resolvedInstance {
return resolvedInstance
}

do {
service = try container.resolve(type: Service.self,
forCustomTypeIdentifier: customTypeIdentifier,
factoryParameters: factoryParameters,
resolver: container)
return service!
resolvedInstance = try container.resolve(type: Injected.self,
forCustomTypeIdentifier: customTypeIdentifier,
factoryParameters: factoryParameters,
resolver: container)
return resolvedInstance!
} catch {
fatalError("DI fatal error :\(error)")
}
Expand All @@ -75,7 +75,7 @@ import Foundation
}

/// The property wrapper itself for debugging and testing
public var projectedValue: LazyInjectService {
public var projectedValue: LazyInject {
self
}
}
Loading
Loading