DIKit Dependency Injection Framework for Swift, inspired by KOIN.

Overview

DIKit

Dependency Injection Framework for Swift, inspired by KOIN. Basically an implementation of service-locator pattern, living within the application's context.

Grow as you go!

We started small, it perfectly fits our use case.

Installation

Via Carthage

DIKit can be installed using Carthage. After installing Carthage just add DIKit to your Cartfile:

1.6.1 ">
github "Liftric/DIKit" ~> 1.6.1

Via CocoaPods

CocoaPods is a dependency manager for Swift and Objective-C Cocoa projects. After installing CocoaPods add DIKit to your Podfile:

platform :ios, '9.0'
pod 'DIKit', '~> 1.6.1'

Basic usage

  1. Define some sub DependencyContainer (basically some sort of module declaration):
import DIKit

public extension DependencyContainer {
    static var backend = module {
        single { Backend() as BackendProtocol }
    }
}

public extension DependencyContainer {
    static var network = module {
        single { Network() as NetworkProtocol }
    }
}

public extension DependenyContainer {
    static var app = module {
        single { AppState() as AppStateProtocol }
        factory { StopWatch() as StopWatchProtocol }
    }
}
  1. Set the root DependencyContainer and set it before the application gets initialised:
import DIKit

@UIApplicationMain
class AppDelegate: UIApplicationDelegate {
    override init() {
        super.init()
        DependencyContainer.defined(by: modules { .backend; .network; .app })
    }
}

Without sub DependencyContainer the following shorthand writing also does the job:

import DIKit

@UIApplicationMain
class AppDelegate: UIApplicationDelegate {
    override init() {
        super.init()
        DependencyContainer.defined(by: module {
            single { AppState() as AppStateProtocol }
            factory { StopWatch() as StopWatchProtocol }
        })
    }
}
  1. Inject the dependencies, for instance in a module:
import DIKit

class Backend: BackendProtocol {
    @Inject var network: NetworkProtocol
}

or a ViewController:

import DIKit

class FirstViewController: UIViewController {
    // MARK: - Dependencies
    @LazyInject var backend: BackendProtocol
    @OptionalInject var stopwatch: StopWatchProtocol?

    // MARK: - View lifecycle
    override func viewWillAppear(_ animated: Bool) {
        let result = backend.fetch()
        print(result)
    }
}

Injection via constructor:

import DIKit

struct AppState: AppStateProtocol {
    private let backend: BackendProtocol
    init(backend: BackendProtocol = resolve()) {
        self.backend = backend
    }
}

Advanced usage

Resolving by Tag

When registering your dependencies you can optionally define a tag. The tag can be anything, as long as it is AnyHashable.

This way you can register different resolvable dependencies for the same Type.

enum StorageContext: String {
    case userdata
    case systemdata
}

public extension DependencyContainer {
    static var app = module {
        factory(tag: StorageContext.systemdata) { LocalStorage() as LocalStorageProtocol }
        factory(tag: StorageContext.userdata) { LocalStorage() as LocalStorageProtocol }
    }
}

You can then reference the same tag when resolving the type and can thus resolve different instances. Referencing the tag works with all injection methods.

import DIKit

class Backend: BackendProtocol {
    @Inject(tag: StorageContext.systemdata) var injectedStorage: LocalStorageProtocol
    @LazyInject(tag: StorageContext.systemdata) var lazyInjectedStorage: LocalStorageProtocol
    @OptionalInject(tag: StorageContext.systemdata) var optionalInjectedStorage: LocalStorageProtocol?
    
    private let constructorInjectedStorage: LocalStorageProtocol
    init(storage: LocalStorageProtocol = resolve(tag: StorageContext.systemdata)) {
        self.constructorInjectedStorage = storage
    }
}
Comments
  • WIP chore: adding ci via bitrise

    WIP chore: adding ci via bitrise

    Why was this change made (Body)

    Add a CI tool for automated testing and building to protect against breaking changes.

    Bitrise

    Bitrise is an automation tool that can will build, test, distribute etc., It's also free.

    Why is this a WIP/Draft?

    Trying a few CI tools out.

    Testing BitRise PR comments. This may not work because I could only point it to my fork of the repo.

    Footer

    • [x] There are no breaking changes

    From Issue 21

    opened by JZDesign 14
  • Add the option to resolve dependencies by name

    Add the option to resolve dependencies by name

    Implements #8

    This PR add functionality to resolve dependencies by name or in this case tag. In order to do so, I changed the following:

    A component no longer has a tag: String but an let identifier: AnyHashable. This identifier is used to reference the Component as well as the instance in the Container (ComponentStack and InstanceStack). I also added a ComponentIdentifier struct that uses the Type of the factory as well as an optional tag to calculate the hashValue. So instead of a String of the Type, we now use the hashValue of the type to identify a Component or an instance in our Container.

    Next I added an optional tag: AnyHashable parameter to all functions that are used to register or resolve dependencies.

    I didn't write any documentation in the README yet since I first wanted to get some feedback about this first. I am looking forward to hear ideas on how this could be improved.

    opened by brototyp 8
  • Consider Scope.prototype renaming

    Consider Scope.prototype renaming

    I stumbled upon the Scope enum and found the prototype case a bit misleading:

    /// Defines the `Component` scope.
    public enum Scope {
        /// The same instance of the class is returned each time it is injected.
        case singleton
    
        /// A new instance of the class is created each time it is injected.
        case prototype // Consider renaming this to `clone` or `instance`?
    }
    
    // I'd consider prototype as a term for some form of an abstract class
    // the attached documentation above even describes the behavior as creating an instance
    container.register(as: .prototype) { ComponentX() }
    
    // I'd found one of these more meaningful
    container.register(as: .clone) { ComponentX() }
    container.register(as: .instance) { ComponentX() }
    
    enhancement 
    opened by guidoschmidt 5
  • Thoughts: loss of types

    Thoughts: loss of types

    When playing around with the example application I kind of stumbled upon a potential issue with the implementation itself:

    Having a DependencyContainer register a service:

    public extension DependencyContainer {
        static var app: DependencyContainer {
            return DependencyContainer { container in
                container.register(as: .prototype) { 
                     // This will register LocalStorage explicitly with it's protocol
                     LocalStorage() as LocalStorageProtocol
                }
            }
        }
    }
    

    The type of the registered service/component kind of gets lost when using the inject() method: As a developer I may not know what type of services are registered :thinking:

    class ModalViewController: UIViewController {
        let storage: LocalStorageProtocol = inject() // This will compile and will run
        let storage: LocalStorage = inject() // This will compile but won't run
    
        @IBAction func close(_ sender: Any) {
            dismiss(animated: true)
        }
    }
    

    I'm not sure if that's possible from my current understanding of the DIKit code but having something like inject(ofType: LocalStorage) could help? Maybe the resolve() function could be improved to use stacks of components/instances that are better typed that using a String representation? 🤔

    enhancement 
    opened by guidoschmidt 3
  • Constructor dependency injection

    Constructor dependency injection

    Currently, a propertyWrapper can neither be used on local variables nor on a constructor / functions. There is already a possibility to use constructor dependency injection like:

    let backend: BackendProtocol
    init(backend: BackendProtocol = resolve()) {
       self.backend = backend
    }
    
    enhancement help wanted 
    opened by benjohnde 2
  • Rename Injectable to Inject

    Rename Injectable to Inject

    Why was this change made (Body)

    In response to issue #25.

    Footer

    • [x] There are no breaking changes
      • If not why and what: breaks the API.

    From Issue 25

    opened by ishansharma 2
  • feat: adds PR template

    feat: adds PR template

    Why was this change made (Body)

    During my first PR against master I was made aware that there was a format I didn't adhere to that was called out in the Contrubuting Documentation. To help other's not make the same mistake this adds a PR template that will automatically add this markdown format to each PR opened. There are comments in the markdown that link to the contributing documentation for ease of access.

    Footer

    • [x] There are no breaking changes If not why and what:
    opened by JZDesign 2
  • SwiftUI no AppDelegate support

    SwiftUI no AppDelegate support

    Hi I'm trying to use this library with the new SwiftUI lifecycle that doesn't have an AppDelegate, it seemed ok to initialize it in the "App" class (the one with @main annotation) but when i do, it throws an error "Fatal error: root DependencyContainer is not yet set."

    @main
    class ChopsticksApp: App {
        @Inject var viewModel: StartupViewModel
        
        required init() {
            FirebaseApp.configure()
            DependencyContainer.defined(by: modules { .main; .restaurant; .common; .restaurantList })
        }
        
        var body: some Scene {
            WindowGroup {
                
            }
        }
    }
    

    Also this method of initialization is giving another error Schermata 2021-07-05 alle 01 35 38

    The modules are defined like this

    public extension DependencyContainer {
        static var main = module {
            single { FirebaseApp.app() as FirebaseApp? }
        }
    }
    
    opened by sedestrian 1
  • Documentation for resolving dependencies by tag

    Documentation for resolving dependencies by tag

    This is the documentation for ticket #8 and PR #39.

    I also added a super simple example to the example project. Nothing complex but should show one possible way to use it.

    opened by brototyp 1
  • Rethink lazy injection

    Rethink lazy injection

    Currently lazy injection works through a different computed propery behaviour. Due to using an enum for @Inject it is mutating. Hence it is not really useful in a struct, as we are not allowed to alter a variable within a struct Cannot use mutating getter on immutable value: function call returns immutable value. And as 'mutating' may only be used on 'func' declarations we need a different approach.

    bug question 
    opened by benjohnde 1
  • Lazy injection and new container resolvement

    Lazy injection and new container resolvement

    As we are not allowed to invoke UIApplication.shared.delegate on anything but the main thread, we need some workaround, especially for the other derivatives, like macOS, tvOS, etc.!

    Thus, we use some kind of dirty static variable to get along with those issues.

    Besides these changes, I also include lazy injection and bit of syntactic sugar for that.

    opened by benjohnde 0
  • Defining dependencies with arguments

    Defining dependencies with arguments

    This PR is a draft to have a discussion if this is a thing we want to add at all and how.

    This PR adds support for dependencies with arguments. I am looking forward for any feedback and thoughts about this.

    Usage

    // Defining a resolvable
    factory { LocalStorage(name: "default") as LocalStorageProtocol }
    factory { name in LocalStorage(name: name) as LocalStorageProtocol }
    factory { (args: (name: String, other: Int)) in LocalStorage(name: args.name) as LocalStorageProtocol }
    
    // Resolving a depdendency
    @Inject var defaultLocalStorage: LocalStorageProtocol
    @Inject("With just one parameter") var localStorageWithJustOneParameter: LocalStorageProtocol
    @Inject((name: "Local Storage with multiple parameters", other: 1)) var localStorageWithMultipleParameters: LocalStorageProtocol
    
    let localStorage: LocalStorageProtocol = resolve("The name of the storage")
    
    func doSomething(_ storage: LocalStorageProtocol = resolve("storage name")) { }
    

    Implementation details

    • Only factories can have an argument
    • One cannot define a resolvable with a tag and arguments
    • It is possible to define multiple resolvables of the same type with different arguments
    • resolvables can only have a single argument. Multiple arguments can be resolved using tuples
    • The type of the resolvable at definition time must match exactly the one at resolution time. E.g. String != String?or (String, Int) != (name: String, age: Int).

    Todos

    • [ ] Add documentation in code
    • [ ] Add documentation in the Readme
    opened by brototyp 0
  • Bug when resolving some dependencies

    Bug when resolving some dependencies

    Hello, i have a bug when trying to resolve a dependency that i have in multiple targets

    My componentStack is this:

    (lldb) po self.componentStack
    ▿ 8 elements
      ▿ 0 : 2 elements
        ▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: Restaurants.RestaurantsLocalDataSource))
          ▿ value : ComponentIdentifier
            - tag : nil
            - type : Restaurants.RestaurantsLocalDataSource
        ▿ value : <Component<RestaurantsLocalDataSource>: 0x600000b0c060>
      ▿ 1 : 2 elements
        ▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: Restaurants.RestaurantsRemoteDataSource))
          ▿ value : ComponentIdentifier
            - tag : nil
            - type : Restaurants.RestaurantsRemoteDataSource
        ▿ value : <Component<RestaurantsRemoteDataSource>: 0x600000b0c120>
      ▿ 2 : 2 elements
        ▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: RestaurantList.RestaurantListViewModel))
          ▿ value : ComponentIdentifier
            - tag : nil
            - type : RestaurantList.RestaurantListViewModel
        ▿ value : <Component<RestaurantListViewModel>: 0x600000b14360>
      ▿ 3 : 2 elements
        ▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: Chopsticks.StartupViewModel))
          ▿ value : ComponentIdentifier
            - tag : nil
            - type : Chopsticks.StartupViewModel
        ▿ value : <Component<StartupViewModel>: 0x600000b043c0>
      ▿ 4 : 2 elements
        ▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: Swift.Optional<__C.FIRApp>))
          ▿ value : ComponentIdentifier
            - tag : nil
            - type : Swift.Optional<__C.FIRApp>
        ▿ value : <Component<Optional<FIRApp>>: 0x600000b04360>
      ▿ 5 : 2 elements
        ▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: RealmSwift.Realm))
          ▿ value : ComponentIdentifier
            - tag : nil
            - type : RealmSwift.Realm
        ▿ value : <Component<Realm>: 0x600000b14240>
      ▿ 6 : 2 elements
        ▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: Restaurants.RestaurantRepository))
          ▿ value : ComponentIdentifier
            - tag : nil
            - type : Restaurants.RestaurantRepository
        ▿ value : <Component<RestaurantRepository>: 0x600000b0c000>
      ▿ 7 : 2 elements
        ▿ key : AnyHashable(DIKit.ComponentIdentifier(tag: nil, type: FIRFirestore))
          ▿ value : ComponentIdentifier
            - tag : nil
            - type : FIRFirestore
        ▿ value : <Component<FIRFirestore>: 0x600000b14300>
    
    

    The identifier is this:

    (lldb) po identifier
    ▿ ComponentIdentifier
      - tag : nil
      - type : FIRFirestore
    

    But this still finds no matching component in the stack. I have a module providing the dependency in one target (dynamic framework) of my app, and the dependency itself is requested in another target

    Schermata 2021-07-09 alle 15 32 00 Schermata 2021-07-09 alle 15 32 31
    opened by sedestrian 0
  • Runtime module override

    Runtime module override

    I'm trying to cover a use case when the root module needs to be rebuilt due to an API environment change. Once the network client changes, the affected modules should be redefined.

    In Koin there's a loadKoinModules that lets the developer replace affected modules after startKoin is called (reset cached data when the user logs out).

    Let me know if you had something similar in mind and I'm happy to contribute.

    This would also fix #42

    opened by magyarosibotond 0
  • @OptionalInject does not work with actual optional

    @OptionalInject does not work with actual optional

    The @OptionalInject wrapper only works if one provides a non-optional component. If the component is optional, the @OptionalInject wrapped instance always ends up nil.

    This appears to be because the component is registered using its optional type (e.g. Optional<ComponentF>) but then the resolve fails as it is looking for non-optional type (e.g. ComponentF).

    This can be demonstrated in DIKitTests testOptionalInjection by making ComponentF's init optional.

    optional_init

    Doing this causes the test to fail.

    opened by bgeerdes 0
Releases(1.6.1)
Owner
null
Cleanse is a dependency injection framework for Swift.

Cleanse - Swift Dependency Injection Cleanse is a dependency injection framework for Swift. It is designed from the ground-up with developer experienc

Square 1.7k Dec 16, 2022
Swinject is a lightweight dependency injection framework for Swift.

Swinject Swinject is a lightweight dependency injection framework for Swift. Dependency injection (DI) is a software design pattern that implements In

null 5.6k Dec 31, 2022
Dependency Injection framework for Swift (iOS/macOS/Linux)

Declarative, easy-to-use and safe Dependency Injection framework for Swift (iOS/macOS/Linux) Features Dependency declaration via property wrappers or

Scribd 684 Dec 12, 2022
Swift Ultralight Dependency Injection / Service Locator framework

Swift Ultralight Dependency Injection / Service Locator framework

Michael Long 1.9k Jan 6, 2023
Needle - Compile-time safe Swift dependency injection framework

Needle is a dependency injection (DI) system for Swift. Unlike other DI frameworks, such as Cleanse, Swinject, Needle encourages hierarchical DI struc

Uber Open Source 1.4k Jan 3, 2023
CarbonGraph - A Swift dependency injection / lookup framework for iOS

CarbonGraph is a Swift dependency injection / lookup framework for iOS. You can

Baidu 244 Jan 4, 2023
Container is a lightweight dependency injection framework for Swift.

Container Container is a lightweight dependency injection framework for Swift. Installation is available in the Swift Package Manager. Swift Package M

Aleksei Artemev 17 Oct 13, 2022
DIContainer Swift is an ultra-light dependency injection container made to help developers to handle dependencies easily. It works with Swift 5.1 or above.

?? DIContainer Swift It is an ultra-light dependency injection container made to help developers to handle dependencies easily. We know that handle wi

Victor Carvalho Tavernari 10 Nov 23, 2022
Pilgrim - Dependency injection for Swift (iOS, OSX, Linux). Strongly typed, pure Swift successor to Typhoon.

pilgrim.ph Pilgrim is a dependency injection library for Swift with the following features: Minimal runtime-only library that works with pure Swift (s

AppsQuick.ly 60 Oct 24, 2022
Injector - A Swift package for simple dependency injection that also supports Swift UI previews

A Swift package for simple dependency injection that also supports Swift UI prev

null 6 Aug 9, 2022
Tranquillity is a lightweight but powerful dependency injection library for swift.

DITranquillity Tranquillity is a lightweight but powerful dependency injection library for swift. The name "Tranquillity" laid the foundation in the b

Ivlev Alexander 393 Dec 24, 2022
Kraken - Simple Dependency Injection container for Swift. Use protocols to resolve dependencies with easy-to-use syntax!

Kraken Photo courtesy of www.krakenstudios.blogspot.com Introduction Kraken is a simple Dependency Injection Container. It's aimed to be as simple as

Syed Sabir Salman-Al-Musawi 1 Oct 9, 2020
ViperServices - Simple dependency injection container for services written for iOS in swift supporting boot order

ViperServices Introduction ViperServices is dependency injection container for iOS applications written in Swift. It is more lightweight and simple in

Siarhei Ladzeika 5 Dec 8, 2022
Toledo - a dependency injection library for Swift that statically generates resolvers at compile-time.

Toledo Toledo is a dependency injection library for Swift that statically generates resolvers at compile-time. Index Features Installation Usage Licen

Valentin Radu 18 Nov 25, 2022
A new approach to Container-Based Dependency Injection for Swift and SwiftUI.

A new approach to Container-Based Dependency Injection for Swift and SwiftUI. Why do something new? Resolver was my first Dependency Injection system.

Michael Long 573 Jan 2, 2023
Effortless modular dependency injection for Swift.

Inject Effortless modular dependency injection for Swift. Sometimes during the app development process we need to replace instances of classes or acto

Maxim Bazarov 40 Dec 6, 2022
Corridor A Coreader-like Dependency Injection μFramework

Corridor A Coreader-like Dependency Injection μFramework Table of Contents Why | Examples | Usage | Installation | Credits & License | Why In order to

symentis GmbH 60 Nov 1, 2022
Deli is an easy-to-use Dependency Injection Container that creates DI containers

Deli is an easy-to-use Dependency Injection Container that creates DI containers with all required registrations and corresponding factories.

Jungwon An 134 Aug 10, 2022
Dip is a simple Dependency Injection Container.

Dip is a simple Dependency Injection Container. It's aimed to be as simple as possible yet p

Olivier Halligon 949 Jan 3, 2023