Skip to content

Commit

Permalink
cleaned some code and added SwiftUI property wrappers
Browse files Browse the repository at this point in the history
  • Loading branch information
NikSativa committed Jun 9, 2024
1 parent f274cc9 commit 667ae6a
Show file tree
Hide file tree
Showing 30 changed files with 466 additions and 427 deletions.
60 changes: 53 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,59 @@ Swift library that allows you to use a dependency injection pattern in your proj
Most recommended way to create a container is to use assemblies. Assemblies are classes that conform to `Assembly` protocol and are responsible for registering dependencies in the container.

```swift
Container(shared: true,
assemblies: [
FoundationAssembly(),
APIAssembli(),
DataBaseAssembly(),
ThemeAssembly()
])
Container(assemblies: [
FoundationAssembly(),
APIAssembli(),
DataBaseAssembly(),
ThemeAssembly()
])
```

## SwiftUI

If you want to use DIKit in SwiftUI you can create a container in the `App` struct like this:

```swift
@main
struct MyApp: App {
let container = Container(assemblies: [...])

var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(container.toObservable())
}
}
}

// somewhere in the code
struct ContentView: View {
@EnvironmentLazy var api: API

var body: some View {
Button("Make request") {
api.request()
}
}
}
```

## Shared container

If you want to use the container in property wrappers (InjectLazy, InjectProvider..) you can create a shared container like this:

```swift
let container = Container(assemblies: [...])
container.makeShared() // <-- make container shared

// somewhere in the code
final class SomeManager {
@InjectLazy var api: API

func makeRequest() {
api.request()
}
}
```

## Create assembly
Expand Down
35 changes: 19 additions & 16 deletions Source/Arguments.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,36 @@ public struct Arguments {
self.elements = []
}

public func optionalResolve<T>(_: T.Type, at index: Int) -> T? {
public func optionalResolve<T>(_: T.Type = T.self, at index: Int = 0) -> T? {
return elements.indices.contains(index) ? elements[index] as? T : nil
}

public func optionalResolve<T>(at index: Int) -> T? {
optionalResolve(T.self, at: index)
public func resolve<T>(_ type: T.Type = T.self, at index: Int = 0) -> T {
return optionalResolve(type, at: index).unsafelyUnwrapped
}

public func resolve<T>(_: T.Type, at index: Int) -> T {
// swiftlint:disable:next force_cast
elements[index] as! T
public subscript<T>(index: Int) -> T {
return optionalResolve(T.self, at: index).unsafelyUnwrapped
}

public func resolve<T>(at index: Int) -> T {
resolve(T.self, at: index)
public func optionalFirst<T>(_: T.Type = T.self) -> T? {
for element in elements {
if let resolved = element as? T {
return resolved
}
}
return nil
}

public subscript<T>(index: Int) -> T {
// swiftlint:disable:next force_cast
elements[index] as! T
public func first<T>(_: T.Type = T.self) -> T {
return optionalFirst().unsafelyUnwrapped
}
}

public func optionalFirst<T>(_: T.Type = T.self) -> T? {
elements.lazy.compactMap { $0 as? T }.first
}
// MARK: - ExpressibleByArrayLiteral

public func first<T>(_ type: T.Type = T.self) -> T {
optionalFirst(type)!
extension Arguments: ExpressibleByArrayLiteral {
public init(arrayLiteral elements: Any...) {
self.init(elements)
}
}
98 changes: 31 additions & 67 deletions Source/Container.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,53 @@ public final class Container {
private typealias Storyboardable = (Any, Resolver) -> Void
private var storages: [String: Storage] = [:]

public init(shared: Bool = true,
assemblies: [Assembly]) {
public init(assemblies: [Assembly]) {
let allAssemblies = assemblies.flatMap(\.allDependencies).unified()
for assembly in allAssemblies {
assembly.assemble(with: self)
}

if shared {
makeShared()
}

#if os(iOS)
register(ViewControllerFactory.self, options: .container) { [unowned self] _, _ in
Impl.ViewControllerFactory(container: self)
register(ViewControllerFactory.self, options: .transient) { resolver, _ in
Impl.ViewControllerFactory(resolver: resolver)
}
#endif
}

public convenience init(assemblies: Assembly...) {
self.init(assemblies: assemblies)
}

deinit {
if InjectSettings.container === self {
InjectSettings.container = nil
}
}

public convenience init(shared: Bool = false, assemblies: Assembly...) {
self.init(shared: shared, assemblies: assemblies)
/// Register shared container. Return `true` when correctly registered, otherwise `false`
/// Manually access to shared container is not recommended, but it's possible `InjectSettings.container`
@discardableResult
public func makeShared() -> Bool {
if InjectSettings.container.isNil {
InjectSettings.container = self
return true
}
return false
}

/// Unregister shared container. if it's the same instance return `true` otherwise `false`
@discardableResult
public func razeShared() -> Bool {
if InjectSettings.container === self {
InjectSettings.container = nil
return true
}
return false
}

/// Unregister shared container. No matter what instance is registered...
public static func razeShared() {
InjectSettings.container = nil
}

private func key(_ type: some Any, name: String?) -> String {
Expand Down Expand Up @@ -92,61 +113,11 @@ extension Container: Registrator {

return Forwarder(container: self, storage: storage)
}

// MARK: - Any

/// register structs/valueType
@discardableResult
public func registerAny<T>(_ type: T.Type,
name: String?,
accessLevel: Options.AccessLevel,
entity: @escaping (Resolver, _ arguments: Arguments) -> T) -> Forwarding {
let key = key(type, name: name)

if let found = storages[key] {
switch found.accessLevel {
case .final:
assertionFailure("\(type) is already registered with \(key)")
case .open:
break
}
}

let storage: Storage = TransientStorage(accessLevel: .open, generator: entity)
storages[key] = storage
return Forwarder(container: self, storage: storage)
}

public func registrationOfAny(for type: (some Any).Type,
name: String?) -> Forwarding {
let key = key(type, name: name)

guard let storage = storages[key] else {
fatalError("can't resolve dependency of <\(type)>")
}

return Forwarder(container: self, storage: storage)
}
}

// MARK: - ForwardRegistrator

extension Container: ForwardRegistrator {
func registerAny(_ type: (some Any).Type, named: String?, storage: Storage) {
let key = key(type, name: named)

if let found = storages[key] {
switch found.accessLevel {
case .final:
assertionFailure("\(type) is already registered with \(key)")
case .open:
break
}
}

storages[key] = storage
}

func register(_ type: (some Any).Type, named: String?, storage: Storage) {
let key = key(type, name: named)

Expand Down Expand Up @@ -176,13 +147,6 @@ extension Container: Resolver {
}
}

extension Container /* Storyboardable */ {
private func makeShared() {
assert(InjectSettings.container.isNil, "storyboard handler was registered twice")
InjectSettings.container = self
}
}

private extension String {
var unwrapped: String {
if hasPrefix("Swift.Optional<") {
Expand Down
17 changes: 1 addition & 16 deletions Source/Forwarder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,17 @@ import Foundation
public protocol Forwarding {
@discardableResult
func implements<T>(_ type: T.Type, named: String?, accessLevel: Options.AccessLevel?) -> Self

@discardableResult
func implementsAny<T>(_ type: T.Type, named: String?, accessLevel: Options.AccessLevel?) -> Self
}

public extension Forwarding {
@discardableResult
func implements<T>(_ type: T.Type = T.self, named: String? = nil, accessLevel: Options.AccessLevel? = nil) -> Self {
return implements(type, named: named, accessLevel: accessLevel)
}

@discardableResult
func implementsAny<T>(_ type: T.Type = T.self, named: String? = nil, accessLevel: Options.AccessLevel? = nil) -> Self {
return implementsAny(type, named: named, accessLevel: accessLevel)
}
}

protocol ForwardRegistrator: AnyObject {
func register<T>(_ type: T.Type, named: String?, storage: Storage)
func registerAny<T>(_ type: T.Type, named: String?, storage: Storage)
}

struct Forwarder: Forwarding {
Expand All @@ -36,14 +27,8 @@ struct Forwarder: Forwarding {
}

@discardableResult
func implements(_ type: (some Any).Type, named: String?, accessLevel: Options.AccessLevel?) -> Self {
func implements<T>(_ type: T.Type, named: String?, accessLevel: Options.AccessLevel?) -> Self {
container.register(type, named: named, storage: ForwardingStorage(storage: storage, accessLevel: accessLevel))
return self
}

@discardableResult
func implementsAny(_ type: (some Any).Type, named: String?, accessLevel: Options.AccessLevel?) -> Self {
container.registerAny(type, named: named, storage: ForwardingStorage(storage: storage, accessLevel: accessLevel))
return self
}
}
4 changes: 2 additions & 2 deletions Source/InjectSettings.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Foundation

public enum InjectSettings {
internal static var container: Container? {
public internal(set) static var container: Container? {
didSet {
assert(oldValue == nil)
assert(oldValue == nil, "Container is already registered")
}
}

Expand Down
4 changes: 1 addition & 3 deletions Source/PropertyWrapper/EnvironmentLazy.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#if canImport(SwiftUI)

import Foundation
import SwiftUI

Expand Down Expand Up @@ -29,8 +28,7 @@ public struct EnvironmentLazy<Value>: DynamicProperty {
}
}

private final class Holder<Value> {
private final class Holder<Value>: ObservableObject {
var instance: Value?
}

#endif
2 changes: 0 additions & 2 deletions Source/PropertyWrapper/EnvironmentProvider.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#if canImport(SwiftUI)

import Foundation
import SwiftUI

Expand All @@ -21,5 +20,4 @@ public struct EnvironmentProvider<Value>: DynamicProperty {
self.arguments = arguments
}
}

#endif
7 changes: 5 additions & 2 deletions Source/PropertyWrapper/Inject.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ public struct Inject<Value> {
}

public init(named: String? = nil, with arguments: Arguments = .init()) {
assert(InjectSettings.resolver != nil)
self.wrappedValue = InjectSettings.resolver!.resolve(named: named, with: arguments)
guard let resolver = InjectSettings.resolver else {
fatalError("Container is not shared")
}

self.wrappedValue = resolver.resolve(named: named, with: arguments)
}
}
7 changes: 5 additions & 2 deletions Source/PropertyWrapper/InjectLazy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ public struct InjectLazy<Value> {
}

public init(named: String? = nil, with arguments: Arguments = .init()) {
assert(InjectSettings.resolver != nil)
guard let resolver = InjectSettings.resolver else {
fatalError("Container is not shared")
}

self.projectedValue = .init(with: {
return InjectSettings.resolver!.resolve(named: named, with: arguments)
return resolver.resolve(named: named, with: arguments)
})
}
}
7 changes: 5 additions & 2 deletions Source/PropertyWrapper/InjectProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ public struct InjectProvider<Value> {
}

public init(named: String? = nil, with arguments: Arguments = .init()) {
assert(InjectSettings.resolver != nil)
guard let resolver = InjectSettings.resolver else {
fatalError("Container is not shared")
}

self.projectedValue = .init(with: {
return InjectSettings.resolver!.resolve(named: named, with: arguments)
return resolver.resolve(named: named, with: arguments)
})
}
}
7 changes: 5 additions & 2 deletions Source/PropertyWrapper/InjectWrapped.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ public struct InjectWrapped<Value: InstanceWrapper> {
}

public init(named: String? = nil, with arguments: Arguments = .init()) {
assert(InjectSettings.resolver != nil)
self.wrappedValue = InjectSettings.resolver!.resolveWrapped(named: named, with: arguments)
guard let resolver = InjectSettings.resolver else {
fatalError("Container is not shared")
}

self.wrappedValue = resolver.resolveWrapped(named: named, with: arguments)
}
}
Loading

0 comments on commit 667ae6a

Please sign in to comment.