Swiftpack.co - Package - vknabel/EasyInject

CocoaPods CocoaPods Install License

EasyInject

EasyInject is designed to be an easy to use, lightweight composition and dependency injection library. Instead of injecting instances for specific types, you provide instances for keys, without losing any type information. This enables its Injectors to be used as a composable, dynamic and typesafe data structure. It may be comparable with a Dictionary that may contain several types, without losing type safety.

Check out the generated docs at vknabel.github.io/EasyInject.

EasyInject supports Swift 3 and Swift 4 since version 1.2.0. Values can only accessed by subscripts in Swift 4, if you are still using Swift 3, keep using Injector.resolving(for:).

Installation

EasyInject is a Swift only project and supports Swift Package Manager, Carthage and CocoaPods.

Swift Package Manager

import PackageDescription

let package = Package(
    name: "YourPackage",
    dependencies: [
        .Package(url: "https://github.com/vknabel/EasyInject.git", majorVersion: 1)
    ]
)

Carthage

github "vknabel/EasyInject" ~> 1.2

CocoaPods

source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!

pod 'EasyInject', '~> 1.2'

Introduction

In order to inject your dependencies, you first need to prepare your key by implementing Hashable.

import EasyInject

enum ServicesKeyType { } // will never be instantiated
typealias Services = GenericProvidableKey<Services>

Now we need to define our keys, by setting up Providers with Strings and our type hints.

extension Provider {
    static var baseUrl: Provider<Services, String> {
        return Provider<Services, String>(for: "baseUrl")
    }
    static var networkService: Provider<Services, NetworkService> {
        // produces a key of `networkService(...) -> Network`.
        return .derive()
    }
    static var dataManager: Provider<Services, DataManager> {
        return .derive()
    }
}

final class NetworkService {
    let baseUrl: String
    init<I: Injector where I.Key == Services>(injector: inout I) throws {
        print("Start: NetworkService")
        baseUrl = try injector[.baseUrl] // or resolving(from: .baseUrl) in Swift 3.x
        print("Finish: NetworkService")
    }
}
final class DataManager {
    let networkService: NetworkService
    init<I: Injector where I.Key == Services>(injector: inout I) throws {
        print("Start: DataManager")
        networkService = try injector[.networkService]
        print("Finish: DataManager")
    }
}

LazyInjector

There are some Injectors to choose, like a StrictInjector or LazyInjector. Let's pick the lazy one first and provide some values for our keys.

var lazyInjector = LazyInjector<Services>() // Only Services keys will fit in here
lazyInjector.provide(for: .baseUrl, usingFactory: { _ in
    print("Return: BasUrl")
    return "https://my.base.url/"
})
lazyInjector.provide(for: .dataManager, usingFactory: DataManager.init)
lazyInjector.provide(for: .networkService, usingFactory: NetworkService.init)

Since we are using the LazyInjector, no closure we passed has been executed yet. They will only be executed when they get resolved.

// this will execute all factories we passed for our providers
do {
    try lazyInjector.resolve(from: .dataManager)
} catch {
    print("Error: \(error)")
}

Because we picked LazyInjector, all dependencies will be resolved automatically, when needed. Therefore the produced output would be:

Start: DataManager
Start: NetworkService
Return: BasUrl
Finish: NetworkService
Finish: DataManager

So because of the laziness of out LazyInjector, all dependencies will be resolved automatically. Cyclic dependencies throw an error on being resolved to prevent endless recursions.

StrictInjector

The previous example would fail when using StrictInjector, because we provided .dataManager before providing .networkService, but DataManager requires a .networkService.

var strictInjector = StrictInjector<Services>()
strictInjector.provide(for: .baseUrl, usingFactory: { _ in
    print("Return: BaseUrl")
    return "https://my.base.url/"
})
strictInjector.provide(for: .dataManager, usingFactory: DataManager.init) // <-- missing .networkService
strictInjector.provide(for: .networkService, usingFactory: NetworkService.init)
do {
    try strictInjector.resolve(from: .dataManager)
} catch {
    print("Error: \(error)")
}

The output would be:

Return: BaseUrl
Start: DataManager
Start: NetworkService
Finish: NetworkService
Error: keyNotProvided("networkService(...) -> NetworkService")

This behavior may be helpful when debugging your LazyInjector in order to detect dependency cycles.

You may fix this error, just by flipping the lines with .networkService and .dataManager, and that would lead to the following output:

Return: BaseUrl
Start: NetworkService
Finish: NetworkService
Start: DataManager
Finish: DataManager
strictInjector = StrictInjector<Services>()
strictInjector.provide(for: .baseUrl, usingFactory: { _ in
    print("Return: BaseUrl")
    return "https://my.base.url/"
})
strictInjector.provide(for: .networkService, usingFactory: NetworkService.init)
strictInjector.provide(for: .dataManager, usingFactory: DataManager.init)
do {
    try strictInjector.resolve(from: .dataManager)
} catch {
    print("Error: \(error)")
}

GlobalInjector

A GobalInjector wraps another Injector in order to make it act like a class.

let globalInjector = GlobalInjector(injector: strictInjector)
let second = globalInjector
// `globalInjector` may be mutated as it is a class.
second.provide("https://vknabel.github.io/EasyInject", for: .baseUrl)

if let left = try? globalInjector.resolve(from: .baseUrl),
let right = try? globalInjector.resolve(from: .baseUrl),
left == right {
    // both `right` and `left` contain `"https://vknabel.github.io/EasyInject"` for `.baseUrl` due to reference semantics
}

ComposedInjector

A ComposedInjector consists of two other Injectors. The call .resolve(from:) will target the .left Injector and on failure, the .right one. .provide(for:,usingFactory:) defaults to .provideLeft(for:,usingFactory:) which will provide the factory only to the .left one.

Usually the left Injector will be the local one, whereas the right one is a global one. This makes it possible to cascade ComposedInjectors from your root controller down to your leaf controllers.

var composedInjector = ComposedInjector(left: StrictInjector(), right: globalInjector)
composedInjector.provideLeft("https://vknabel.github.io/EasyInject/Structs/ComposedInjector.html", for: .baseUrl)
do {
    try composedInjector.resolveBoth(from: .baseUrl)
    // returns `("https://vknabel.github.io/EasyInject/Structs/ComposedInjector.html", "https://vknabel.github.io/EasyInject")`
} catch {
    print("Error: \(error)")
}

Author

Valentin Knabel, dev@vknabel.com

License

EasyInject is available under the MIT license.

Github

link
Stars: 2

Dependencies

Used By

Total: 0

Releases

1.4.0 Swift 5-Package-Syntax - 2020-10-03 16:02:20

Released: 2020-10-03

  • Upgraded to Swift 5-Package-Syntax - @vknabel

1.3.0 Swift 4 Package Syntax - 2020-10-03 15:58:57

Released: 2020-03-14

  • Upgraded to Swift 4-Package-Syntax - @vknabel

1.1.0 - 2017-10-06 19:56:53

1.1.0

Released: 2016-10-24

API Additions

  • Implemented CustomDebugStringConvertible for Provider, GenericProvidableKey - @vknabel
  • GenericProvidableKey now implemented CustomStringConvertible - @vknabel

Other Changes

  • Changed internal representation for .derive - @vknabel
  • Replaced protocol Providable with an typealias for Any (and therefore no adoption is required) - @vknabel

1.2.0 - 2017-10-06 19:55:23

1.2.0

Released: 2017-10-06

API Additions

  • Generic subscripts for Injector. - @vknabel

Other Changes

  • Support for Swift 4.0 while retaining Swift 3 support. - @vknabel

Stable Release - 2016-10-20 20:39:54

1.0.0

Released: 2016-10-18

Breaking Changes

  • Added new case InjectionError.cyclicDependency (#1). - @vknabel

API Additions

  • Added GenericProvidableKey which lets you define custom types in a typealias. - @vknabel
  • Detection of cyclic dependencies in LazyInjector (#1). - @vknabel
  • Provider.derive(_:) will now work for all ExpressibleByStringLiteral where K.StringLiteralType == String (previously only for String) - @vknabel

Other Changes

  • Added some basic guides to generated Jazzy docs - @vknabel

Updated Docs - 2016-09-26 11:09:11

0.8.1

Released: 2016-09-26

Other Changes

  • Updated Docs - @vknabel

Drops Swift 2.x - 2016-09-08 16:03:52

0.8.0

Released: 2016-09-08

Breaking Changes:

  • Dropped Swift 2.x Support - @vknabel

Unit Tests - 2016-08-23 20:02:05

0.7.0

Released: 23/08/2016

Breaking Changes:

  • ComposedInjector throws an aggregated InjectionError.keyNotFound(_) as expected - @vknabel
  • Removed enum case InjectionError.invalidInjection(key:injected:expected:) - @vknabel

API Additions:

  • Added convenience GlobalInjector.init() - @vknabel
  • Added Injector.revoking(for:) that takes a Provider - @vknabel
  • Added Injector.revoke(for:) that takes a Provider - @vknabel

Other Changes:

  • Added Unit Tests - @vknabel
  • Set up Travis CI - @vknabel

Release 0.6.0 - 2016-08-16 17:37:54

0.6.0

Released: 16/08/2016

Breaking Changes:

  • Updated Swift 3.0 to Beta 6 - @vknabel

Other Changes:

  • Fixes some warnings in Swift 2 and 3 - @vknabel

Rejecting Keys - 2016-08-10 07:29:12

Released: 10/08/2016

Breaking Changes:

  • Added Injector.revoking(key:) - @vknabel
  • Added MutableInjector.revoke(key:) - @vknabel

API Additions:

  • Injector.revoking(key:) will be implemented by InjectorDerivingFromMutableInjector - @vknabel
  • Added revoke{Left|Right|Both}(key:) and revoking{Left|Right|Both}(key:) to ComposedInjector - @vknabel

Other Changes:

  • Updated descriptions in README.md, EasyInject.podspec and Play.playground - @vknabel

Provided Keys - 2016-08-09 18:42:06

Released: 09/08/2016

Breaking Changes:

  • Added Injector.providedKeys - @vknabel

API Additions:

  • Added default implementation for value type for InjectorDerivingFromMutableInjector.copy() - @vknabel

Other Changes:

  • Documented 100% - @vknabel
  • Renamed Changelog.md to CHANGELOG.md - @vknabel

- 2016-08-04 18:18:30

Breaking API Changes:

  • Removed AnyMutableInjector, instead use AnyInjector - @vknabel
  • AnyInjector, GlobalInjector, ComposedInjector now only depend on the ProvidableKey instead of complete Injectors - @vknabel
  • Injector now requires resolving(key:) and providing(key:,usingFactory:) - @vknabel
  • MutableInjector now requires resolve(key:) and provide(key:,usingFactory:) - @vknabel

API Additions:

  • AnyInjector additionally conforms to MutableInjector - @vknabel
  • Added globalize(), erase(), compose(_:) that wrap a MutableInjector into another one - @vknabel
  • Added globalized(), erased(), composed(_:) that wrap a Injector into another one - @vknabel

Other Changes:

  • The old methods resolving(from:), providing(for:,usingFactory:) have been moved to an extension - @vknabel
  • The old methods resolve(from:), provide(for:,usingFactory:) have been moved to an extension - @vknabel
  • GlobalInjector and ComposedInjector now use AnyInjector internally - @vknabel
  • Reincluded docs into the repo - @vknabel

ComposedInjector - 2016-07-30 07:55:40