Skip to content

Commit

Permalink
new documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
NikSativa committed Oct 22, 2024
1 parent 2628e19 commit 5757cac
Show file tree
Hide file tree
Showing 25 changed files with 359 additions and 88 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# DIKit - Dependency Injection Kit
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FNikSativa%2FDIKit%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/NikSativa/DIKit)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FNikSativa%2FDIKit%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/NikSativa/DIKit)
[![CI](https://github.com/NikSativa/DIKit/actions/workflows/swift_macos.yml/badge.svg)](https://github.com/NikSativa/DIKit/actions/workflows/swift_macos.yml)

Swift library that allows you to use a dependency injection pattern in your project by creating a container that holds all the dependencies in one place.

Expand Down
11 changes: 11 additions & 0 deletions Source/Arguments.swift
Original file line number Diff line number Diff line change
@@ -1,36 +1,44 @@
import Foundation

/// A container that stores arguments for resolving dependencies.
@MainActor
public struct Arguments {
private let elements: [Any]

/// Initializes a new container with the specified elements.
@MainActor
public init(_ elements: [Any]) {
self.elements = elements
}

/// Initializes a new container with the specified elements.
@MainActor
public init(_ elements: Any...) {
self.elements = elements
}

/// Initializes a new container without elements.
@MainActor
public init() {
self.elements = []
}

/// Resolves an element of the specified type at the specified index or returns nil.
public func optionalResolve<T>(_: T.Type = T.self, at index: Int = 0) -> T? {
return elements.indices.contains(index) ? elements[index] as? T : nil
}

/// Resolves an element of the specified type at the specified index.
public func resolve<T>(_ type: T.Type = T.self, at index: Int = 0) -> T {
return optionalResolve(type, at: index).unsafelyUnwrapped
}

/// Resolves an element of the specified type at the specified index by subscript.
public subscript<T>(index: Int) -> T {
return optionalResolve(T.self, at: index).unsafelyUnwrapped
}

/// Finds the first element of the specified type or returns nil.
public func optionalFirst<T>(_: T.Type = T.self) -> T? {
for element in elements {
if let resolved = element as? T {
Expand All @@ -40,14 +48,17 @@ public struct Arguments {
return nil
}

/// Finds the first element of the specified type.
public func first<T>(_: T.Type = T.self) -> T {
return optionalFirst().unsafelyUnwrapped
}

/// Number of elements in the container.
public var count: Int {
return elements.count
}

/// A Boolean value indicating whether the container is empty.
public var isEmpty: Bool {
return elements.isEmpty
}
Expand Down
6 changes: 4 additions & 2 deletions Source/Assembly.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import Foundation

/// An assembly that assembles dependencies for future container
@MainActor
public protocol Assembly {
/// unique identifier of the assembly that will be used to used to remove duplicates from the Container
/// Unique identifier of the assembly that will be used to used to remove duplicates from the Container
/// - Warning: override it on your own risk
var id: String { get }

/// list of assemblies that this assembly depends on
/// List of assemblies that this assembly depends on
var dependencies: [Assembly] { get }

/// Assembles dependencies for the specified registrator
func assemble(with registrator: Registrator)
}

Expand Down
18 changes: 11 additions & 7 deletions Source/Container.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import Foundation

/// A container that stores dependencies and resolves them.
/// - Important: Assemblies order is important
@MainActor
public final class Container {
private typealias Storyboardable = (Any, Resolver) -> Void
private var storages: [String: Storage] = [:]

/// Initializes a new container with the specified assemblies.
/// - Important: Assemblies order is important
@MainActor
public init(assemblies: [Assembly]) {
let allAssemblies = assemblies.flatMap(\.allDependencies).unified()
Expand All @@ -13,6 +17,8 @@ public final class Container {
}
}

/// Initializes a new container with the specified assemblies
/// - Important: Assemblies order is important
@MainActor
public convenience init(assemblies: Assembly...) {
self.init(assemblies: assemblies)
Expand All @@ -24,10 +30,11 @@ public final class Container {
}
}

// Register ViewController factory. Available only for iOS platform
#if os(iOS) && canImport(UIKit)
public func registerViewControllerFactory() {
register(ViewControllerFactory.self, options: .transient) { resolver, _ in
Impl.ViewControllerFactory(resolver: resolver)
return ViewControllerFactoryImpl(resolver: resolver)
}
}
#endif
Expand Down Expand Up @@ -76,9 +83,7 @@ extension Container: Registrator {

/// register classes
@discardableResult
public func register<T>(_ type: T.Type,
options: Options,
entity: @MainActor @escaping (Resolver, _ arguments: Arguments) -> T) -> Forwarding {
public func register<T>(type: T.Type, options: Options, entity: @MainActor @escaping (Resolver, _ arguments: Arguments) -> T) -> Forwarding {
let key = key(type, name: options.name)

if let found = storages[key] {
Expand Down Expand Up @@ -108,8 +113,7 @@ extension Container: Registrator {
return Forwarder(container: self, storage: storage)
}

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

guard let storage = storages[key] else {
Expand Down Expand Up @@ -142,7 +146,7 @@ extension Container: ForwardRegistrator {
// MARK: - Resolver

extension Container: Resolver {
public func optionalResolve<T>(_ type: T.Type, named: String?, with arguments: Arguments) -> T? {
public func optionalResolve<T>(type: T.Type, named: String?, with arguments: Arguments) -> T? {
let key = key(type, name: named)
if let storage = storages[key],
let resolved = storage.resolve(with: self, arguments: arguments) as? T {
Expand Down
42 changes: 37 additions & 5 deletions Source/Forwarder.swift
Original file line number Diff line number Diff line change
@@ -1,25 +1,57 @@
import Foundation

/// Protocol for Forwarding behavior
/// Forwarding is a mechanism that allows to forward a type to another type.
/// It is useful when you want to use a type that is already registered in the container to expose it as another type.
///
/// - Example:
/// When you have a protocol `Service` and a class `ServiceImp` that implements it.
/// You can register `ServiceImp` in the container and then forward it to `Service`.
/// So when you resolve `Service` you will get an instance of `ServiceImp`.
///
/// ```swift
/// registrator.register(ServiceImp.self, options: .transient) { resolver in
/// return ServiceImp()
/// }
/// .implements(Service.self)
/// ```
///
/// or you can redirect the type to another type from another assembly, but you must strictly ensure that the dependency is already registered in the container _(registration order is important)_.
///
/// ```swift
/// registrator.registration(for: BuildMode.self)
/// .implements(ApiBuildMode.self)
/// ```
@MainActor
public protocol Forwarding {
/// Registers a type for forwarding with the specified name and access level.
@discardableResult
func implements<T>(_ type: T.Type, named: String?, accessLevel: Options.AccessLevel?) -> Self
func implements<T>(type: T.Type, named: String?, accessLevel: Options.AccessLevel?) -> Self
}

public extension Forwarding {
/// Registers a type for forwarding with the specified name and access level.
///
/// - Example:
/// ```swift
/// registrator.register(Manager.self, options: .named("guest")) {
/// return ManagerImpl()
/// }
/// .implements(AnonymousService.self, named: "anonymous") // <-- forwards the dependency by the name
/// ```
@discardableResult
func implements<T>(_ type: T.Type = T.self, named: String? = nil, accessLevel: Options.AccessLevel? = nil) -> Self {
return implements(type, named: named, accessLevel: accessLevel)
return implements(type: type, named: named, accessLevel: accessLevel)
}
}

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

@MainActor
struct Forwarder: Forwarding {
internal struct Forwarder: Forwarding {
private unowned let container: ForwardRegistrator
private let storage: Storage

Expand All @@ -31,7 +63,7 @@ struct Forwarder: Forwarding {
}

@discardableResult
func implements<T>(_ type: T.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
}
Expand Down
3 changes: 0 additions & 3 deletions Source/Impl.swift

This file was deleted.

6 changes: 6 additions & 0 deletions Source/InjectSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,25 @@ import Foundation

public enum InjectSettings {
#if swift(>=6.0)
/// General container. It is used to resolve dependencies in the application according to your requirements.
/// Make sure that you have called `container.makeShared()` before using it and sure that it called only once.
public internal(set) nonisolated(unsafe) static var container: Container? {
didSet {
assert(oldValue == nil, "Container is already registered")
}
}
#else
/// General container. It is used to resolve dependencies in the application according to your requirements.
/// Make sure that you have called `container.makeShared()` before using it and sure that it called only once.
public internal(set) static var container: Container? {
didSet {
assert(oldValue == nil, "Container is already registered")
}
}
#endif

/// Resolver for the container. It is used to resolve dependencies in the application according to your requirements.
/// Make sure that you have called `container.makeShared()` before using it and sure that it called only once.
public static var resolver: Resolver? {
return container
}
Expand Down
47 changes: 47 additions & 0 deletions Source/ObservableResolver.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Foundation

/// `ObservableResolver` is a wrapper for the resolver that can be observed by SwiftUI views in the environment.
///
/// ```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 {
/// @DIObservedObject var api: API
///
/// var body: some View {
/// Button("Make request") {
/// api.request()
/// }
/// }
/// }
/// ```
@MainActor
public final class ObservableResolver: Resolver, ObservableObject {
private let original: Resolver

init(_ original: Resolver) {
self.original = original
}

public func optionalResolve<T>(type: T.Type, named: String?, with arguments: Arguments) -> T? {
return original.optionalResolve(type, named: named, with: arguments)
}
}

public extension Resolver {
/// Returns an observable resolver that can be observed by SwiftUI views in the environment.
func toObservable() -> ObservableResolver {
return ObservableResolver(self)
}
}
Loading

0 comments on commit 5757cac

Please sign in to comment.