Cleanse is a dependency injection framework for Swift.

Overview

Cleanse - Swift Dependency Injection

Documentation/cleanse_logo_small.png

https://travis-ci.org/square/Cleanse.svg?branch=master https://coveralls.io/repos/github/square/Cleanse/badge.svg?branch=master&asdf

Cleanse is a dependency injection framework for Swift. It is designed from the ground-up with developer experience in mind. It takes inspiration from both Dagger and Guice.

Getting Started

This is a quick guide on how to get started using Cleanse in your application.

A full-fledged example of using Cleanse with Cocoa Touch can be found in Examples/CleanseGithubBrowser

Installation

Using CocoaPods

You can pull in the latest Cleanse version into your Podfile using

pod 'Cleanse'

Using Xcode

Cleanse.xcodeproj can be dragged and dropped into an existing project or workspace in Xcode. One may add Cleanse.framework as a target dependency and embed it.

Using Carthage

Cleanse should be able to be configured with Carthage. One should be able to follow the Adding Frameworks to an Application from Carthage's README to successfully do this.

Using Swift Package Manager

Cleanse can be used with Swift Package Manager. The following a definition that can be added to the dependencies of a Project declaration. Adding Cleanse as a package dependency in Xcode 11 is supported by v4.2.5 and above.

Features

Feature Cleanse Implementation Status
Multi-Bindings Supported (.intoCollection())
Overrides Supported
Objective-C Compatibility layer Supported
Property Injection [1] Supported
Type Qualifiers Supported via Type Tags
Assisted Injection Supported
Subcomponents Supported via Components
Service Provider Interface Supported
cleansec (Cleanse Compiler) Experimental
[1] Property injection is known as field injection in other DI frameworks

Another very important part of a DI framework is how it handles errors. Failing fast is ideal. Cleanse is designed to support fast failure. It currently supports fast failing for some of the more common errors, but it isn't complete

Error Type Cleanse Implementation Status
Missing Providers Supported [2]
Duplicate Bindings Supported
Cycle Detection Supported
[2] When a provider is missing, errors present line numbers, etc. where the provider was required. Cleanse will also collect all errors before failing

Using Cleanse

The Cleanse API is in a Swift module called Cleanse (surprised?). To use any of its API in a file, at the top, one must import it.

import Cleanse

Defining a Component and Root Type

Cleanse is responsible for building a graph (or more specifically a directed acyclic graph) that represents all of your dependencies. This graph starts with a root object which is connected to its immediate dependencies, and those dependencies hold edges to its dependencies and so on until we have a complete picture of your application's object graph.

The entry point into managing your dependencies with Cleanse starts by defining a "Root" object that is returned to you upon construction. In a Cocoa Touch application, our root object could be the rootViewController object we set on the application's UIWindow. (More logically the root object is the App Delegate, however since we don't control construction of that we would have to use Property Injection. You can read more about this in the Advanced Setup guide)

Let's begin by defining the RootComponent:

struct Component : Cleanse.RootComponent {
    // When we call build(()) it will return the Root type, which is a RootViewController instance.
    typealias Root = RootViewController

    // Required function from Cleanse.RootComponent protocol.
    static func configureRoot(binder bind: ReceiptBinder) -> BindingReceipt {

    }

    // Required function from Cleanse.RootComponent protocol.
    static func configure(binder: Binder) {
        // We will fill out contents later.
    }
}

After creating our root component, we find that we're required to implement two functions: static func configureRoot(binder bind: ReceiptBinder) -> BindingReceipt and static func configure(binder: Binder). These functions are very important because they will contain the logic for how we construct every object/dependency in our app. The parameters and return types are confusing right now, but will make more sense as we go along.

The first function is required of any Component since it tells Cleanse how to construct the root object. Let's fill in the contents to configure how we will construct our RootViewController.

static func configureRoot(binder bind: ReceiptBinder) -> BindingReceipt {
    return bind.to(factory: RootViewController.init)
}

Now, let's create our RootViewController class

class RootViewController: UIViewController {
    init() {
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .blue
    }
}

We've successfully wired up our root component! Our root object RootViewController is configured properly, so in our App Delegate we can now build the component (and graph) to use it.

Important: It is important that you retain an instance of the ComponentFactory returned from ComponentFactory.of(:). Otherwise subcomponents may unexpectedly become deallocated.

// IMPORTANT: We must retain an instance of our `ComponentFactory`.
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var factory: ComponentFactory?

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) ->  Bool {
        // Build our root object in our graph.
        factory = try! ComponentFactory.of(AppDelegate.Component.self)
        let rootViewController = factory!.build(())

        // Now we can use the root object in our app.
        window!.rootViewController = rootViewController
        window!.makeKeyAndVisible()

        return true
    }

Satisfying Dependencies

Running the app will now display our RootViewController with a blue background. However this is not very interesting nor realistic as our RootViewController will likely require many dependencies to set up our app. So let's create a simple dependency RootViewProperties that will hold the background color of our root view (among other future properties).

struct RootViewProperties {
    let backgroundColor: UIColor
}

And then inject RootViewProperties into our RootViewContoller and set the background color.

class RootViewController: UIViewController {
    let rootViewProperties: RootViewProperties
    init(rootViewProperties: RootViewProperties) {
        self.rootViewProperties = rootViewProperties
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = rootViewProperties.backgroundColor
    }
}

Running the app now will yield a new error saying a provider for RootViewProperties is missing. That's because we referenced it from our RootViewController class, but Cleanse didn't find a binding for the RootViewProperties type. So let's create one! We will do this inside the static func configure(binder: Binder) function we talked about earlier inside our root component.

static func configure(binder: Binder) {
      binder
          .bind(RootViewProperties.self)
          .to { () -> RootViewProperties in
              RootViewProperties(backgroundColor: .blue)
          }
  }

Now that we have satisfied the RootViewProperties dependency, we should be able to successfully launch and see the same blue background as before.

As the functionality of this app grows, one may add more dependencies to RootViewController as well as more Modules to satisfy them.

It may be worth taking a look at our example app to see a more full-featured example.

Core Concepts & Data Types

Provider/ProviderProtocol

Wraps a value of its containing type. Serves the same functionality as Java's javax.inject.Provider.

Provider and TaggedProvider (see below) implement ProviderProtocol protocol which is defined as:

public protocol ProviderProtocol {
    associatedtype Element
    func get() -> Element
}

Type Tags

In a given component, there may be the desire to provide or require different instances of common types with different significances. Perhaps we need to distinguish the base URL of our API server from the URL of our temp directory.

In Java, this is done with annotations, in particular ones annotated with @Qualifier. In Go, this can be accomplished with tags on structs of fields.

In Cleanse's system a type annotation is equivalent to an implementation of the Tag protocol:

public protocol Tag {
    associatedtype Element
}

The associatedtype, Element, indicates what type the tag is valid to apply to. This is very different than annotations in Java used as qualifiers in Dagger and Guice which cannot be constrained by which type they apply to.

In Cleanse, the Tag protocol is implemented to distinguish a type, and the TaggedProvider is used to wrap a value of Tag.Element. Since most of the library refers to ProviderProtocol, TaggedProvider is accepted almost everywhere a Provider is.

Its definition is almost identical to Provider aside from an additional generic argument:

struct TaggedProvider<Tag : Cleanse.Tag> : ProviderProtocol {
    func get() -> Tag.Element
}

Example

Say one wanted to indicate a URL type, perhaps the base URL for the API endpoints, one could define a tag this way:

public struct PrimaryAPIURL : Tag {
    typealias Element = NSURL
}

Then one may be able to request a TaggedProvider of this special URL by using the type:

TaggedProvider<PrimaryAPIURL>

If we had a class that requires this URL to perform a function, the constructor could be defined like:

class SomethingThatDoesAnAPICall {
    let primaryURL: NSURL
    init(primaryURL: TaggedProvider) {
        self.primaryURL = primaryURL.get()
    }
}

Modules

Modules in Cleanse serve a similar purpose to Modules in other DI systems such as Dagger or Guice. Modules are building blocks for one's object graph. Using modules in Cleanse may look very similar to those familiar with Guice since configuration is done at runtime and the binding DSL is very inspired by Guice's.

The Module protocol has a single method, configure(binder:), and is is defined as:

protocol Module {
    func configure<B : Binder>(binder: B)
}

Examples

Providing the Base API URL
struct PrimaryAPIURLModule : Module {
  func configure(binder: Binder) {
    binder
      .bind(NSURL.self)
      .tagged(with: PrimaryAPIURL.self)
      .to(value: NSURL(string: "https://connect.squareup.com/v2/")!)
  }
}
Consuming the Primary API URL (e.g. "https://connect.squareup.com/v2/")

Note: It is generally a good practice to embed the Module that configures X as an inner struct of X named Module. To disambiguate Cleanse's Module protocol from the inner struct being defined, one has to qualify the protocol with Cleanse.Module

class SomethingThatDoesAnAPICall {
    let primaryURL: NSURL
    init(primaryURL: TaggedProvider) {
        self.primaryURL = primaryURL.get()
    }
    struct Module : Cleanse.Module {
        func configure(binder: Binder) {
            binder
                .bind(SomethingThatDoesAnAPICall.self)
                .to(factory: SomethingThatDoesAnAPICall.init)
        }
    }
}

Components

Cleanse has a concept of a Component. A Component represents an object graph of our dependencies that returns the Root associated type upon construction and is used as the "entry point" into Cleanse. However, we can also use a Component to create a subgraph inside our parent object graph, called a subcomponent. Subcomponents are closely related to scopes and are used to scope your dependencies. Objects inside a component are only allowed to inject dependencies that exist within the same component (or scope), or an ancestor's component. A parent component is not allowed to reach into a subcomponent and retrieve a dependency. One example of using components to scope dependencies is by having a LoggedInComponent inherting from your application's Root component. This allows you to bind logged in specific objects such as session tokens or account objects within the LoggedInComponent so that you can't accidently leak these dependencies into objects used outside of a logged session (i.e welcome flow views).

The base component protocol is defined as:

public protocol ComponentBase {
  /// This is the binding required to construct a new Component. Think of it as somewhat of an initialization value.
  associatedtype Seed = Void

  /// This should be set to the root type of object that is created.
  associatedtype Root

  associatedtype Scope: Cleanse._ScopeBase = Unscoped

  static func configure(binder: Binder<Self.Scope>)

  static func configureRoot(binder bind: ReceiptBinder) -> BindingReceipt
}

The outermost component of an object graph (e.g. the Root component), is built by the build(()) method on ComponentFactory. This is defined as the following protocol extension:

public extension Component {
    /// Builds the component and returns the root object.
    public func build() throws -> Self.Root
}

Examples

Defining a subcomponent
struct RootAPI {
    let somethingUsingTheAPI: SomethingThatDoesAnAPICall
}

struct APIComponent : Component {
    typealias Root = RootAPI
    func configure(binder: Binder) {
        // "include" the modules that create the component
        binder.include(module: PrimaryAPIURLModule.self)
        binder.include(module: SomethingThatDoesAnAPICall.Module.self)
        // bind our root Object
        binder
            .bind(RootAPI.self)
            .to(factory: RootAPI.init)
    }
}
Using the component

Cleanse will automatically create the type ComponentFactory in your object graph by calling binder.install(dependency: APIComponent.self).

struct Root : RootComponent {
    func configure(binder: Binder) {
        binder.install(dependency: APIComponent.self)
    }
    // ...
}

And then you can use it by injecting in the ComponentFactory instance into an object and calling build(()).

class RootViewController: UIViewController {
    let loggedInComponent: ComponentFactory

    init(loggedInComponent: ComponentFactory) {
        self.loggedInComponent = loggedInComponent
        super.init(nibName: nil, bundle: nil)
    }

    func logIn() {
        let apiRoot = loggedInComponent.build(())
    }
}

Assisted Injection

Summary (RFC #112)

Assisted injection is used when combining seeded parameters and pre-bound dependencies. Similar to how a subcomponent has a Seed that is used to build the object graph, assisted injection allows you to eliminate boilerplate by creating a Factory type with a defined Seed object for construction via the build(_:) function.

Examples

Creating a factory

Say we have a detail view controller that displays a particular customer's information based on the user's selection from a list view controller.

class CustomerDetailViewController: UIViewController {
    let customerID: String
    let customerService: CustomerService
    init(customerID: Assisted<String>, customerService: CustomerService) {
        self.customerID = customerID.get()
        self.customerService = customerService
    }
    ...
}

In our initializer, we have Assisted which represents an assisted injection parameter based on the customer ID selected from the list view controller, and a pre-bound dependency CustomerService.

In order to create our factory, we need to define a type that conforms to AssistedFactory to set our Seed and Element types.

extension CustomerDetailViewController {
    struct Seed: AssistedFactory {
        typealias Seed = String
        typealias Element = CustomerDetailViewController
    }
}

Once we create our AssistedFactory object, we can create the factory binding through Cleanse.

extension CustomerDetailViewController {
    struct Module: Cleanse.Module {
        static func configure(binder: Binder) {
            binder
              .bindFactory(CustomerDetailViewController.self)
              .with(AssistedFactory.self)
              .to(factory: CustomerDetailViewController.init)
        }
    }
}
Consuming our factory

After creating our binding, Cleanse will bind a Factory type into our object graph. So in our customer list view controller, consuming this factory may look like:

class CustomerListViewController: UIViewController {
    let detailViewControllerFactory: Factory

    init(detailViewControllerFactory: Factory) {
        self.detailViewControllerFactory = detailViewControllerFactory
    }
    ...

    func tappedCustomer(with customerID: String) {
        let detailVC = detailViewControllerFactory.build(customerID)
        self.present(detailVC, animated: false)
    }
}

Service Provider Interface

Summary (RFC #118)

Cleanse provides a plugin interface that developers can use to hook into the generated object graph to create custom validations and tooling.

Creating a plugin can be done in 3 steps:

1. Create your plugin implementation by conforming to the protocol CleanseBindingPlugin

You will be required to implement the function func visit(root: ComponentBinding, errorReporter: CleanseErrorReporter), which hands you an instance of a ComponentBinding and CleanseErrorReporter.

The first parameter, ComponentBinding, is a representation of the root component and can be used to traverse the entire object graph. The second, CleanseErrorReporter is used to report errors back to the user after validation is complete.

2. Register your plugin with a CleanseServiceLoader instance

After creating an instance of a CleanseServiceLoader, you can register your plugin via the register(_:) function.

3. Pass your service loader into the RootComponent factory function

The RootComponent factory function, public static func of(_:validate:serviceLoader:) accepts a CleanseServiceLoader instance and will run all the plugins registered within that object.

NOTE: Your plugins will only be run if you set validate to true in the factory function.

Sample plugin implementations are available in the RFC linked above.

Binder

A Binder instance is what is passed to Module.configure(binder:) which module implementations use to configure their providers.

Binders have two core methods that one will generally interface with. The first, and simpler one, is the install method. One passes it an instance of a module to be installed. It is used like:

binder.include(module: PrimaryAPIURLModule.self)

It essentially tells the binder to call configure(binder:) on PrimaryAPIURLModule.

The other core method that binders expose is the bind(type: E.Type). This is the entry point to configure a binding. The bind methods takes one argument, which the metattype of the element being configured. bind() returns a BindingBuilder that one must call methods on to complete the configuration of the binding that was initiated.

bind() and subsequent builder methods that are not terminating are annotated with @warn_unused_result to prevent errors by only partially configuring a binding.

The type argument of bind() has a default and can be inferred and omitted in some common cases. In this documentation we sometimes specify it explicitly to improve readability.

BindingBuilder and Configuring Your Bindings

The BindingBuilder is a fluent API for configuring your bindings. It is built in a way that guides one through the process of configuring a binding through code completion. A simplified grammar for the DSL of BindingBuilder is:

binder
  .bind([Element.self])                // Bind Step
 [.tagged(with: Tag_For_Element.self)] // Tag step
 [.sharedInScope()]                    // Scope step
 {.to(provider:) |                     // Terminating step
  .to(factory:)  |
  .to(value:)}

Bind Step

This starts the binding process to define how an instance of Element is created

Tag Step (Optional)

An optional step that indicates that the provided type should actually be TaggedProvider and not just Provider.

See: Type Tags for more information

Scope Step

By default, whenever an object is requested, Cleanse constructs a new one. If the optional .sharedInScope() is specified, Cleanse will memoize and return the same instance in the scope of the Component it was configured in. Each Component requires its own Scope type. So if this is configured as a singleton in the RootComponent, then will return the same instance for the entire app.

Cleanse provides two scopes for you: Unscoped and Singleton. Unscoped is the default scope that will always construct a new object, and Singleton is provided out of convenience but not necessary to use. It is most commonly used as the scope type for your application's RootComponent.

Terminating Step

To finish configuring a binding, one must invoke one of the terminating methods on BindingBuilder. There are multiple methods that are considered terminating steps. The common ones are described below.

Dependency-Free Terminating methods

This is a category of terminating methods that configure how to instantiate elements that don't have dependencies on other instances configured in the object graph.

Terminating Method: to(provider: Provider)

Other terminating methods funnel into this. If the binding of Element is terminated with this variant, .get() will be invoked on the on the provider argument when an instance of Element is requested.

Terminating Method: to(value: E)

This is a convenience method. It is semantically equivalent to .to(provider: Provider(value: value)) or .to(factory: { value }). It may offer performance advantages in the future, but currently doesn't.

Terminating Method: to(factory: () -> E) (0th arity)

This takes a closure instead of a provider, but is otherwise equivalent. Is equivalent to .to(provider: Provider(getter: factory))

Dependency-Requesting Terminating Methods

This is how we define requirements for bindings. Dagger 2 determines requirements at compile time by looking at the arguments of @Provides methods and @Inject constructors. Guice does something similar, but using reflection to determine arguments. One can explicitly request a dependency from Guice's binder via the getProvider() method.

Unlike Java, Swift doesn't have annotation processors to do this at compile time, nor does it have a stable reflection API. We also don't want to expose a getProvider()-like method since it allows one to do dangerous things and also one loses important information on which providers depend on other providers.

Swift does, however, have a very powerful generic system. We leverage this to provide safety and simplicity when creating our bindings.

Terminating Methods: to(factory: (P1) -> E) (1st arity)

This registers a binding of E to the factory function which takes one argument.

How it works

Say we have a hamburger defined as:

struct Hamburger {
   let topping: Topping
   // Note: this actually would be created implicitly for structs
   init(topping: Topping) {
     self.topping = topping
   }
 }

When one references the initializer without calling it (e.g. let factory = Hamburger.init), the expression results in a function type of

(topping: Topping) -> Hamburger

So when configuring its creation in a module, calling

binder.bind(Hamburger.self).to(factory: Hamburger.init)

will result in calling the .to(factory: (P1) -> E) terminating function and resolve Element to Hamburger and P1 to Topping.

A pseudo-implementation of this to(factory:):

public func to<P1>(factory: (P1) -> Element) {
  // Ask the binder for a provider of P1. This provider
  // is invalid until the component is constructed
  // Note that getProvider is an internal method, unlike in Guice.
  // It also specifies which binding this provider is for to
  // improve debugging.
  let dependencyProvider1: Provider =
      binder.getProvider(P1.self, requiredFor: Element.self)

  // Create a Provider of Element. This will call the factory
  // method with the providers
  let elementProvider: Provider<Element> = Provider {
      factory(dependencyProvider1.get())
  }

  // Call the to(provider:) terminating function to finish
  // this binding
  to(provider: elementProvider)
}

Since the requesting of the dependent providers happen at configuration time, the object graph is aware of all the bindings and dependencies at configuration time and will fail fast.

Terminating Methods: to(factory: (P1, P2, … PN) -> E) (Nth arity)

Well, we may have more than one requirement to construct a given instance. There aren't variadic generics in swift. However we used a small script to generate various arities of the to(factory:) methods.

Collection Bindings

It is sometimes desirable to provide multiple objects of the same type into one collection. A very common use of this would be providing interceptors or filters to an RPC library. In an app, one may want to add to a set of view controllers of a tab bar controller, or settings in a settings page.

This concept is referred to as Multibindings in Dagger and in Guice.

Providing to a Set or Dictionary is not an unwanted feature and could probably be built as an extension on top of providing to Arrays.

Binding an element to a collection is very similar to standard Bind Steps, but with the addition of one step: calling .intoCollection() in the builder definition.:

binder
  .bind([Element.self])                // Bind Step
  .intoCollection()   // indicates that we are providing an
                    // element or elements into Array**
 [.tagged(with: Tag_For_Element.self)]   // Tag step
 [.asSingleton()]                        // Scope step
 {.to(provider:) |                       // Terminating step
  .to(factory:)  |
  .to(value:)}

The Terminating Step for this builder sequence can either be a factory/value/provider of a single Element or Array of Elements.

Property Injection

There are a few instances where one does not control the construction of an object, but dependency injection would be deemed useful. Some of the more common occurrences of this are:

  • App Delegate: This is required in every iOS app and is the entry point, but UIKit will construct it.
  • View Controllers constructed via storyboard (in particular via segues): Yes, we all make mistakes. One of those mistakes may have been using Storyboards before they became unwieldy. One does not control the construction of view controllers when using storyboards.
  • XCTestCase: We don't control how they're instantiated, but may want to access objects from an object graph. This is more desirable in higher levels of testing such as UI and integration testing (DI can usually be avoided for lower level unit tests)

Cleanse has a solution for this: Property injection (known as Member injection in Guice and Dagger).

In cleanse, Property injection is a second class citizen by design. Factory/Constructor injection should be used wherever possible, but when it won't property injection may be used. Property Injection has a builder language, similar to the BindingBuilder:

binder
  .bindPropertyInjectionOf(<metatype of class being injected into>)
  .to(injector: <property injection method>)

There are two variants of the terminating function, one is where the signature is

(Element, P1, P2,  ..., Pn) -> ()

And the other is

(Element) -> (P1, P2, ..., Pn) -> ()

The former is to allow for simple injection methods that aren't instance methods, for example:

binder
  .bindPropertyInjectionOf(AClass.self)
  .to {
     $0.a = ($1 as TaggedProvider<ATag>).get()
  }

or

binder
  .bindPropertyInjectionOf(BClass.self)
  .to {
      $0.injectProperties(superInjector: $1, b: $2, crazyStruct: $3)
  }

The latter type of injection method that can be used (Element -> (P1, P2, …, Pn) -> ()) is convenient when referring to instant methods on the target for injection.

Say we have

class FreeBeer {
  var string1: String!
  var string2: String!

  func injectProperties(
    string1: TaggedProvider,
    string2: TaggedProvider
  ) {
    self.string1 = string1.get()
    self.string2 = string2.get()
  }
}

One can bind a property injection for FreeBeer by doing:

binder
  .bindPropertyInjectionOf(FreeBeer.self)
  .to(injector: FreeBeer.injectProperties)

The result type of the expression FreeBeer.injectProperties is FreeBeer -> (TaggedProvider, TaggedProvider) -> ()

After binding a property injector for Element, one will be able to request the type PropertyInjector in a factory argument. This has a single method defined as:

func injectProperties(into instance: Element)

Which will then perform property injection into Element.

Note: Property injectors in the non-legacy API are unaware of class hierarchies. If one wants property injection to cascade up a class hierarchy, the injector bound may call the inject method for super, or request a PropertyInjector as an injector argument and use that.

Advanced Setup

We can make the root of our Cleanse object graph the App Delegate through Property Injection. We must use property injection here because we don't control construction of the app delegate. Now we can model our "Root" as an instance of PropertyInjector and then use this object to inject properties into our already constructed App Delegate.

Let's start by redefining the RootComponent:

extension AppDelegate {
  struct Component : Cleanse.RootComponent {
    // When we call build() it will return the Root type, which is a PropertyInjector.
    // More on how we use the PropertyInjector type later.
    typealias Root = PropertyInjector

    // Required function from Cleanse.RootComponent protocol.
    static func configureRoot(binder bind: ReceiptBinder>) -> BindingReceipt> {
        return bind.propertyInjector(configuredWith: { bind in
            bind.to(injector: AppDelegate.injectProperties)
        })
    }

    // Required function from Cleanse.RootComponent protocol.
    static func configure(binder: Binder) {
        // Binding go here.
    }
  }
}

Inside of our app delegate, we add the function injectProperties:

func injectProperties(_ window: UIWindow) {
  self.window = window
}

Now to wire up our new root object, we can call injectProperties(:) on ourself in the app delegate:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Build our component, and make the property injector
    let propertyInjector = try! ComponentFactory.of(AppDelegate.Component.self).build(())

     // Now inject the properties into ourselves
    propertyInjector.injectProperties(into: self)

    window!.makeKeyAndVisible()

    return true
}

Running the app now will yield a new error saying a provider for UIWindow is missing, but after binding an instance of our UIWindow and its dependencies, we should be good to go!

extension UIWindow {
  struct Module : Cleanse.Module {
    public func configure(binder: Binder) {
      binder
        .bind(UIWindow.self)
        // The root app window should only be constructed once.
        .sharedInScope()
        .to { (rootViewController: RootViewController) in
          let window = UIWindow(frame: UIScreen.mainScreen().bounds)
          window.rootViewController = rootViewController
          return window
        }
    }
  }
}

Contributing

We're glad you're interested in Cleanse, and we'd love to see where you take it.

Any contributors to the master Cleanse repository must sign the Individual Contributor License Agreement (CLA). It's a short form that covers our bases and makes sure you're eligible to contribute.

License

Apache 2.0

Comments
  • How to solve dependency cycle / property injection doesn't seem to work.

    How to solve dependency cycle / property injection doesn't seem to work.

    I'm having issues with dependency cycle. My Coordinator needs a LoginViewController and my LoginViewController needs a Coordinator to function. But I haven't found a way to get this to work with constructor injection. I also tried propertyInject, but the propertyInjection method doesn't get called. Below you'll see an example of my implementation.

    protocol HomeRoute {
       func routeHome()
    }
    
    protocol RegisterRoute {
       func routeRegister()
    }
    
    class Coordinator: HomeRoute, RegisterRoute {
    
       private let loginViewControllerProvider: Provider<LoginViewController>
    
       init(loginViewControllerProvider: Provider<LoginViewController>) {
          self. loginViewControllerProvider = loginViewControllerProvider
       }
    
       func routeHome() {
          // show Home
       }
    
       func routeRegister() {
          // show Register
       }
    }
    
    class LoginViewController {
       typealias Coordinator = HomeRoute & RegisterRoute
       
       private var coordinator: Coordinator
    
       init(coordinator: Coordinator) {
           self.coordinator = coordinator
       }
    
       // Tryout with propertyInjection (is not being called)
       func injectProperties(_ coordinator: Coordinator) {
           self.coordinator = coordinator
       }
    }
    
    extension Modules {
        struct App: Cleanse.Module {
            static func configure(binder: AppBinder) {
                // Bind Coordinator (BTW is there a way to bind multiple variations of protocols to the same Coordinator? e.g. my RegisterCoordinator might only need a Coordinator confirming to HomeRoute protocol but should get the same Coordinator as the Login)
                binder.bind(LoginViewController.Coordinator.self).sharedInScope().to(factory: ApplicationCoordinator.init)
    
                binder.bind().to(factory: LoginViewController.init)
    
                // When running the app, this doesn't seem to do anything.
                binder.bindPropertyInjectionOf(LoginViewController.self).to(injector: LoginViewController.injectProperties) 
    
            }  
        } 
    }
    
    bug 
    opened by TomVanDerSpek 25
  • Integration with Swift 5.2 Features?

    Integration with Swift 5.2 Features?

    Is there a way to make use of Swift 5.1/2's PropertyWrappers to allow something along the lines of

        @Injected var userStateMachine: UserStateMachine
        @Injected var keyValueStore: KeyValueStore
        @Injected var bundle: BundleProviding
        @Injected var touchIdService: TouchIDManaging
        @Injected var status: SystemStatusProviding
        ...
    }
    

    rather than requiring all parameters for a class to go through the initializer?

    opened by corban123 20
  • Making configure static

    Making configure static

    Also makes Components constructed via ComponentFactory now

    WIP separating validation logic from Graph. Sub-components are now easily validatable

    cc @holmes @sebastianv1

    opened by mikelikespie 18
  • [Not Ready/Require Help] - Swift 4 Update

    [Not Ready/Require Help] - Swift 4 Update

    Added

    • Integrated fastlane to automate the build process
    • Added swiftlint fastlane task
    • lint script to run in xcode build phase

    Changed

    • updated travis config file
    • updated podspec

    Remaining

    • Fix the tests: I'm not really familiar with the Cleanse API but the tests don't compile for one of two reasons:
    1. the build function is missing the seed parameter
    2. ambiguous use of bind
    • Is the Makefile used at all? I fixed the clean task (from swift build --clean to swift package clean. I also changed the build step from:
    generated_sources: Cleanse/BinderArities.swift Cleanse/PropertyInjectionArities.swift
    
    Cleanse/BinderArities.swift: CleanseGen/GenerateBinderArities.swift
    	xcrun swift CleanseGen/GenerateBinderArities.swift > Cleanse/BinderArities.swift
    
    Cleanse/PropertyInjectionArities.swift: CleanseGen/GeneratePropertyInjectionArities.swift
    	xcrun swift CleanseGen/GeneratePropertyInjectionArities.swift > Cleanse/PropertyInjectionArities.swift
    

    to this:

    generated_sources: Cleanse/BinderArities.swift Cleanse/PropertyInjectionArities.swift
    
    Cleanse/BinderArities.swift: CleanseGen/main.swift
    	xcrun swift CleanseGen/main.swift > Cleanse/BinderArities.swift
    
    Cleanse/PropertyInjectionArities.swift: CleanseGen/main.swift
    	xcrun swift CleanseGen/main.swift > Cleanse/PropertyInjectionArities.swift
    

    and that seems to work? But I'm not sure.

    Additionally: where is sphinx being installed? (assuming its referring to this: http://www.sphinx-doc.org/en/stable/tutorial.html) Same question for jazzy, there's nothing in the Gemfile to install it and when I added it the the Gemfile, bundle install is failing.

    opened by thebarndog 16
  • Building with Swift 4 Blocked on SR-6108

    Building with Swift 4 Blocked on SR-6108

    Reference: https://bugs.swift.org/browse/SR-6108

    It appears there is a bug in Swift as a result of SE-0110 (stronger type safety for tuples) that affects our BinderArities methods. Cleanse fails to compile tests because of the following compiler error:

    error: ambiguous use of 'to(file:line:function:factory:)'
                binder.bind().to(factory: StructWithDependencies.init)
                       ^
    Cleanse.BindToable:9:17: note: found this candidate
        public func to<P_1>(file: StaticString = #file, line: Int = #line, function: StaticString = #function, factory: @escaping (P_1) -> Self.Input) -> Cleanse.BindingReceipt<Self.Input>
                    ^
    Cleanse.BindToable:37:17: note: found this candidate
        public func to<P_1, P_2, P_3, P_4, P_5>(file: StaticString = #file, line: Int = #line, function: StaticString = #function, factory: @escaping (P_1, P_2, P_3, P_4, P_5) -> Self.Input) -> Cleanse.BindingReceipt<Self.Input>
    

    For some reason, swift cannot disambiguate between the two generic functions. There is a workaround listed in the JIRA from the swift project, that will resolve the issue if the parameter names of each generic function are unique. So in our case, changing factory: @escaping... to something more unique for each arity such as factory1: @escaping..., factory2: @escaping.

    I don't think this is an acceptable solution for Cleanse so we may have to get creative on this one. Thoughts @holmes @mikelikespie

    opened by sebastianv1 14
  • Swift 3 support

    Swift 3 support

    Thanks for making this project open source. The ideas and concepts look solid and more promising than the existing DI frameworks for Swift.

    From what I see in the source code, you already try to support Swift 3. However it seems that you target one of the early previews of Swift 3. The language has changed very much with the latest swift evolutions and because of that Cleanse doesn't work with the latest beta of Swift 3.

    Are you planning to support Swift 3 in the near future?

    Also, there seems to be some work where you have small corrections in the APIs (e.g. #27) but development seems to have stalled. Do you have some kind of roadmap or another document where you outline future development?

    opened by Lukas-Stuehrk 11
  • What is the right structure for multi-module projects?

    What is the right structure for multi-module projects?

    Hi. I've tried this lib in iOS and got a question about problems people have with using subcomponents in multi-module projects in Android. This article is a good example: https://proandroiddev.com/using-dagger-in-a-multi-module-project-1e6af8f06ffc. In short, Android devs use "dependencies" feature from Dagger2 instead. They create a separate dependency interface and pass it to create new a feature component. That allows to remove the connection between the AppComponent and a Sub Component. There is my example: https://github.com/soniccat/WordTeacher/blob/main/androidApp/src/main/java/com/aglushkov/wordteacher/androidApp/features/di/DefinitionsComponent.kt. How would you do the same with Cleanse?

    Probably for Cleanse that's not a problem and you use it in multi-module projects without any issues. Could you explain the right way of using Cleanse in this case?

    opened by soniccat 7
  • Swift4 Support.

    Swift4 Support.

    Due to SE-0110, all of the binder arity functions become ambiguous with arity-1 and arity-0 to the swift compiler. As a solution in order to unblock using Cleanse with Swift4, renaming just these two functions to include their arity count (to0 and to1) minimizes the surface area of the public API that requires modification. This also makes it much easier to revert back to simply using to when the issue is resolved in Swift.

    Something else worth noting is that you will receive a runtime error (missing binding) if you use the incorrect function name for to0 or to1.

    opened by sebastianv1 7
  • Readme is out-of-date

    Readme is out-of-date

    I am trying to learn and understand the Clean framework. The sample project is nice but not as detailed in explanations as the Readme. When I try to follow the Readme however, it doesn't work; it appears to be out-of-date. I would be happy to update it, but I don't understand the framework enough yet to do so.

    opened by mcmurrym 5
  • Question: Is there a better way to use constructor injection for ViewControllers?

    Question: Is there a better way to use constructor injection for ViewControllers?

    So I have gone through all of example code that I can find on Cleanse, as well as the documentation. As my app can have rather deep navigation at times, I'm struggling with finding a clean solution to injecting dependencies into my view controllers. The problem I'm running into is that with the following scenario:

    ViewControllerA - depends on Service1 - presents ViewControllerB ViewControllerB - depends on Service1 and Service2 - presents ViewControllerC ViewControllerC - depends on Service3 and Service4

    I'm using constructor injection (not using storyboard), but because of the navigation, even though ViewControllerA and ViewControllerB have no dependency on Service3 and Service4, they end up having a dependency on them just so they can pass those dependencies down the navigation chain to ViewControllerC.

    I've toyed with the idea of just using property injection for each of my ViewControllers like I do for AppDelegate and also like I would do if I were using Storyboard, but I really don't want to do that. Am I missing something? Is there a better way to manage this? Or this just something I have to deal with? Coming from Android with Dagger/Dagger2, this just feels wrong.

    opened by CodyDunlap 5
  • Inject a single property into multiple objects

    Inject a single property into multiple objects

    Actually I want to rephrase a question that had already been asked over 3 years ago and closed 2 years later (see issue #45). One of my reasons to return to the subject is the possibility that the Components in the current version might be essentially different from the earlier one.

    I am about to port an Android app relying, among others, on two important components: Dagger 2 and Android ViewModel. One of the strongest motivations for me to try Cleanse is the chance of using the same or similar concepts found in Dagger. For this reason Property Injection (PI) is indispensable in ViewControllers.

    As far as I understand, PI into a specific object of a class A is only supported by a Cleanse Component C[A] bound hard-coded to that class A. If that is the case then PI into n classes would require n components even if all those classes need only a single element of the dependency graph to be injected.

    Could someone confirm the one-to-one limitation in Cleanse? Are there any workarounds? How do you inject a single property into multiple objects?

    opened by dbtech 4
  • Bump tzinfo from 1.2.5 to 1.2.10

    Bump tzinfo from 1.2.5 to 1.2.10

    Bumps tzinfo from 1.2.5 to 1.2.10.

    Release notes

    Sourced from tzinfo's releases.

    v1.2.10

    TZInfo v1.2.10 on RubyGems.org

    v1.2.9

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    TZInfo v1.2.9 on RubyGems.org

    v1.2.8

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    TZInfo v1.2.8 on RubyGems.org

    v1.2.7

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    TZInfo v1.2.7 on RubyGems.org

    v1.2.6

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.

    TZInfo v1.2.6 on RubyGems.org

    Changelog

    Sourced from tzinfo's changelog.

    Version 1.2.10 - 19-Jul-2022

    Version 1.2.9 - 16-Dec-2020

    • Fixed an incorrect InvalidTimezoneIdentifier exception raised when loading a zoneinfo file that includes rules specifying an additional transition to the final defined offset (for example, Africa/Casablanca in version 2018e of the Time Zone Database). #123.

    Version 1.2.8 - 8-Nov-2020

    • Added support for handling "slim" format zoneinfo files that are produced by default by zic version 2020b and later. The POSIX-style TZ string is now used calculate DST transition times after the final defined transition in the file. The 64-bit section is now always used regardless of whether Time has support for 64-bit times. #120.
    • Rubinius is no longer supported.

    Version 1.2.7 - 2-Apr-2020

    • Fixed 'wrong number of arguments' errors when running on JRuby 9.0. #114.
    • Fixed warnings when running on Ruby 2.8. #112.

    Version 1.2.6 - 24-Dec-2019

    • Timezone#strftime('%s', time) will now return the correct number of seconds since the epoch. #91.
    • Removed the unused TZInfo::RubyDataSource::REQUIRE_PATH constant.
    • Fixed "SecurityError: Insecure operation - require" exceptions when loading data with recent Ruby releases in safe mode.
    • Fixed warnings when running on Ruby 2.7. #106 and #111.
    Commits
    • 0814dcd Fix the release date.
    • fd05e2a Preparing v1.2.10.
    • b98c32e Merge branch 'fix-directory-traversal-1.2' into 1.2
    • ac3ee68 Remove unnecessary escaping of + within regex character classes.
    • 9d49bf9 Fix relative path loading tests.
    • 394c381 Remove private_constant for consistency and compatibility.
    • 5e9f990 Exclude Arch Linux's SECURITY file from the time zone index.
    • 17fc9e1 Workaround for 'Permission denied - NUL' errors with JRuby on Windows.
    • 6bd7a51 Update copyright years.
    • 9905ca9 Fix directory traversal in Timezone.get when using Ruby data source
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • API Facelift

    API Facelift

    The public interface for Cleanse was inspired by Java's Guice and feels a bit outdated and less idiomatic. This has created some friction for other considering using Cleanse as a DI solution. I'd like to facelift the API using some of the modern language features (i.e result builders). Put together a functional POC: https://github.com/sebastianv1/SwiftInjector

    static func configure(binder: Binder<Unscoped>) {
         binder.bind(SomeType.self).to { (depA: A) in
             return SomeType(depA: depA)
         }
    }
    

    becomes -->

    static var binding: some SwiftInjector.Binding {
        Group {
            Bind(SomeType.self) { (depA: DepA)
                SomeType(depA: DepA)
            }
        }
    }
    

    Any interest to iterate on the API together @jbeachwood or @chrissonoda ? Would include interop with the old API to support easy migrations.

    opened by sebastianv1 1
  • Bump cocoapods-downloader from 1.3.0 to 1.6.3

    Bump cocoapods-downloader from 1.3.0 to 1.6.3

    Bumps cocoapods-downloader from 1.3.0 to 1.6.3.

    Release notes

    Sourced from cocoapods-downloader's releases.

    1.6.3

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1

    Enhancements
    • None.
    Bug Fixes
    • Fix "can't modify frozen string" errors when pods are integrated using the branch option
      buju77 #10920

    1.5.0

    ... (truncated)

    Changelog

    Sourced from cocoapods-downloader's changelog.

    1.6.3 (2022-04-01)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.2 (2022-03-28)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.1 (2022-03-23)

    Enhancements
    • None.
    Bug Fixes
    • None.

    1.6.0 (2022-03-22)

    Enhancements
    • None.
    Bug Fixes
    • Adds a check for command injections in the input for hg and git.
      orta #124

    1.5.1 (2021-09-07)

    Enhancements
    • None.

    ... (truncated)

    Commits
    • c03e2ed Release 1.6.3
    • f75bccc Disable Bazaar tests due to macOS 12.3 not including python2
    • 52a0d54 Merge pull request #128 from CocoaPods/validate_before_dl
    • d27c983 Ensure that the git pre-processor doesn't accidentally bail also
    • 3adfe1f [CHANGELOG] Add empty Master section
    • 591167a Release 1.6.2
    • d2564c3 Merge pull request #127 from CocoaPods/validate_before_dl
    • 99fec61 Switches where we check for invalid input, to move it inside the download fun...
    • 96679f2 [CHANGELOG] Add empty Master section
    • 3a7c54b Release 1.6.1
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • An example of injection within SceneDelegate that supports SwiftUI (+ thinking)

    An example of injection within SceneDelegate that supports SwiftUI (+ thinking)

    Hi 👋 as iOS app is moving towards multi-window support from iOS 13 an onwards, the example that injects window to the AppDelegate no longer the recommended way. Given that the rising use case of SwiftUI, I made an example that supports SwiftUI and uses SceneDelegate as the root object of the injection.

    https://github.com/DJBen/SceneDelegateInjectionExample

    I am still unconvinced if AppDelegate still should be the root object, but doing so is difficult - the lifecycle of AppDelegate and SceneDelegate seem to be opaque and they do not seem to be dependent on each other. Let me know if I was wrong, but as of today I think making the SceneDelegate root object that instantiates the window makes more sense.

    opened by DJBen 9
  • [Improvement] Better Duplicate Binding Error

    [Improvement] Better Duplicate Binding Error

    We throw a fatalError if there are duplicate bindings for the same type. This can be improved. Likely throw an exception to be handled by the RootComponent of(_:) function.

    enhancement help wanted 
    opened by sebastianv1 0
Releases(4.2.6)
Owner
Square
Square
DIKit Dependency Injection Framework for Swift, inspired by KOIN.

DIKit Dependency Injection Framework for Swift, inspired by KOIN. Basically an implementation of service-locator pattern, living within the applicatio

null 95 Dec 22, 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