Swift Ultralight Dependency Injection / Service Locator framework

Overview

Resolver icon

An ultralight Dependency Injection / Service Locator framework for Swift 5.2 on iOS.

Note that several recent updates to Resolver may break earlier code that used argument passing and/or named services. For more see the Updates section below.

Introduction

Dependency Injection frameworks support the Inversion of Control design pattern. Technical definitions aside, dependency injection pretty much boils down to:

| Giving an object the things it needs to do its job.

That's it. Dependency injection allows us to write code that's loosely coupled, and as such, easier to reuse, to mock, and to test.

For more, see: A Gentle Introduction to Dependency Injection.

Dependency Injection Strategies

There are six classic dependency injection strategies:

  1. Interface Injection
  2. Property Injection
  3. Constructor Injection
  4. Method Injection
  5. Service Locator
  6. Annotation (NEW)

Resolver supports them all. Follow the links for a brief description, examples, and the pros and cons of each.

Property Wrappers

Speaking of Annotations, Resolver now supports resolving services using the new property wrapper syntax in Swift 5.1.

class BasicInjectedViewController: UIViewController {
    @Injected var service: XYZService
    @LazyInjected var service2: XYZLazyService
    @WeakLazyInjected var service3: XYZAnotherLazyService?
}

Just add the Injected keyword and your dependencies will be resolved automatically. See the Annotation documentation for more on this and other strategies.

Features

Resolver is implemented in just over 700 lines of actual code in a single file, but it packs a ton of features into those 700 lines.

TLDR: If nothing else, make sure you read about Automatic Type Inference, Scopes, and Optionals.

Using Resolver

Using Resolver is a simple, three-step process:

  1. Add Resolver to your project.
  2. Register the classes and services your app requires.
  3. Use Resolver to resolve those instances when needed.

Why Resolver?

As mentioned, Resolver is an ultralight Dependency Injection system, implemented in just over 700 lines of code and contained in a single file.

Resolver is also designed for performance. SwinjectStoryboard, for example, is a great dependency injection system, but Resolver clocks out to be about 800% faster at resolving dependency chains than Swinject.

And unlike some other systems, Resolver is written in 100% Swift 5, with no Objective-C code, method swizzling, or internal dependencies on the Objective-C runtime.

Further, Resolver:

Finally, with Automatic Type Inference you also tend to write about 40-60% less dependency injection code using Resolver.

Installation

Resolver supports CocoaPods and the Swift Package Manager.

pod "Resolver"

Resolver itself is just a single source file (Resolver.swift), so it's also easy to simply download the file and add it to your project.

Note that the current version of Resolver (1.4) supports Swift 5.3 and that the minimum version of iOS currently supported with this release is iOS 11.

Read the installation guide for information on supporting earlier versions.

Demo Application

I've made my Builder repositiory public. It's a simple master/detail-style iOS application that contains examples of...

  1. Using the Resolver dependency injection system to construct MVVM architectures.
  2. Using Resolver to mock user data for application development.
  3. Using Resolver to mock user data for unit tests.

I also use it to play with some new code that uses SwiftUI-style builder patterns to constructing the user interface construction and to construct network requests. Check it out.

Resolver Update Notes

It's possible that recent updates to Resolver could cause breaking changes in your code base.

  • Resolver 1.4 improved thread safety and performance. No breaking changes, though accessing Resolver's scopes directly is now deprecated. See: Scopes.

  • Resolver 1.3 adds Name spaces to Resolver. Registering names allows for better autocompletion and makes your code safer by reducing potential runtime evaluation errors. This is a possible breaking change. See: Named Instances

  • Resolver 1.2 changed how arguments are passed to the registration factory in order to provide better support for passing and handling both single and multiple arguments. This is a breaking change. See: Passing and Handling Multiple Arguments

Author

Resolver was designed, implemented, documented, and maintained by Michael Long, a Senior Lead iOS engineer at CRi Solutions. CRi is a leader in developing cutting edge iOS, Android, and mobile web applications and solutions for our corporate and financial clients.

License

Resolver is available under the MIT license. See the LICENSE file for more info.

Additional Resouces

Comments
  • cachedServices is not thread safe

    cachedServices is not thread safe

    Any chance you could make the application cache thread safe?

    In general I only let the strict necessary on the main thread and I use Texture so everything runs in the background except for strict UIKit and some SwiftUI stuff

    Maybe implementing a lock on the cachedServices would help:

                cachedServices[registration.cacheKey]
    
    opened by mycroftcanner 14
  • using InjectedObject as a StateObject

    using InjectedObject as a StateObject

    I couldn't find a way to use the @InjectedObjected property wrapper to act as a @StateObject in the view, after I checked the docs it turned out that @InjectedObject is actually an ObservedObject.

    is there a way to inject an observable object in a SwiftUI view to act as a @StateObject ?

    opened by murad1981 13
  • Best way to use Resolver in SwiftUI previews

    Best way to use Resolver in SwiftUI previews

    What would be the best way to use Resolver in SwiftUI previews? I don't want to use real data (Xcode previews fail anyway when doing that..sometimes) I've been thinking on this and I'm not really sure how to go about it. Ideas?

    opened by Prince2k3 13
  • [Draft] Add `context` registration mutator

    [Draft] Add `context` registration mutator

    Add context registration mutator

    Introduction

    Introducing the new context registration mutator function in ResolverOptions. This mutator allows the @*Injected propertyWrappers to resolve from non-static, user defined container.

    Motivation

    The usage of injection property wrappers is a convenient and safe way of using the framework. Unfortunately there is a compile time issue when the user tries to pass a non-static container to be used for resolution.

    Resolver conveniently avoids this topic by suggesting to use staticly declared containers. For example the following works nicely:

    extension Resolver {
    	static let myFeatureContainer = Resolver(parent: .main)
    	static func registerMyFeatureServices() {
    		myFeatureContainer.register { MyFeatureService() }
    	}
    }
    
    class MyFeatureService { }
    class MyFeature {
    	@Injected(container: .myFeatureContainer) var service: MyFeatureService
    }
    

    Static containers work fine for some projects, but not for others. For example: running more than 1 instance of a feature might cause problems in more complex cases.

    A clean solution is to use non static containers.

    For example:

    extension Resolver {
    	static func registerMyFeatureServices(container: Resolver) {
    		container.register { MyFeatureService() }
    	}
    }
    
    class MyFeatureService { }
    class MyFeature {
    	let container: Container
    	var service: MyFeatureService
    	init() {
    		let container = Resolver(parent: .main)
    		Resolver.registerMyFeatureServices(container: container)
    		self.container = container
    		self.service = container.resolve()
    	}
    }
    

    Although this approach is totally functional, produces a lot of boilerplate code (large initialisers).

    The goal is to combine the clean approach of non-static containers, and the convenience of propertyWrapper injection.

    Proposed solution

    Create a new registration mutator function, that takes a container as its argument. When applied to a registration, the dependencies of the resolved service can access the given container via a static getter that is dedicated for this purpose.

    Example usage:

    extension Resolver {
    	static func registerMyFeatureServices(container: Resolver) {
    		container.register { MyFeatureService() }
    			.context(container)
    	}
    }
    
    class MyFeatureService { }
    class MyFeature {
    	@Injected(container: .context) var service: MyFeatureService
    }
    

    The static container named Resolver.context is read only for the user, references another static container that is not exposed for the user and temporary has the correct value (during resolution).

    While it can be said that it is not very elegant when we look under the hood, looking it from outside feels easy and intuitive to use.

    Effect on API

    The suggested solution is small and backward compatible.

    Alternatives considered

    ProperyWrappers to access a container instance from _enclosingSelf. This has several issues, such as:

    • no official support in Swift for the _enclosingSelf keyword
    opened by ZsoltMolnarrr 9
  • Cannot resolve dependencies when using XCFramework

    Cannot resolve dependencies when using XCFramework

    If I make an XCFramework and try to inject a dependency from that library into another project I will get this error: Fatal error: RESOLVER: 'Service:NONAME' not resolved. To disambiguate optionals use resolver.optional().

    I call Service's register the same why I would if it wasn't an XCFramework but when I switch it to a binary it doesn't work.

    Does Resolver work with binary frameworks?

    opened by sahilreddyifit 9
  • WeakLazyInjected not working on class protocols

    WeakLazyInjected not working on class protocols

    I've got

    @WeakLazyInjected(container: .api) var userAuthAPI: UserAuthenticationAPIType?

    and

    public protocol UserAuthenticationAPIType: class {

    however I still receive

    Generic struct 'WeakLazyInjected' requires that 'UserAuthenticationAPIType' be a class type

    What's going on? Class-only protocols generally work with Weak.

    opened by corban123 9
  • SwiftUI Previews only working in the main app of multi framework application

    SwiftUI Previews only working in the main app of multi framework application

    I'm developing a SwiftUI app with a framework structure like in this picture. The Presentation, Domain and Data Layers are frameworks and the App Layer is the main app.

    I'm registering all dependencies in the main app and it's working fine in simulator or real devices but in preview it gets tricky. When previewing views of the main app (where all the dependencies get registered) everything is fine, too.

    But when trying to preview views of the Presentation layer, the preview crashes because no dependencies are registered. I think this happens because in preview not the complete app runs but only the framework (and it's sub frameworks) of the view your previewing gets executed. Therefore the ResolverRegistering extension doesn't run which registers the dependencies. (At least this would explain why it's only working in the preview of views implemented in the main app.)

    I also tried to register all dependencies in the presentation layer but this only works for the dependencies implemented in this framework and not for these which are implemented in data framework, which the presentation framework has no access to.

    So my question is how to make Resolver notice the dependencies registered in the main app from sub frameworks like presentation layer if running in preview mode?

    opened by theblaggy 8
  • Could not find module 'Resolver' for target 'x86_64-apple-ios-simulator'; found: arm64, arm64-apple-ios-simulator

    Could not find module 'Resolver' for target 'x86_64-apple-ios-simulator'; found: arm64, arm64-apple-ios-simulator

    When you try to build for x86_64 simulator, Resolver can't be found, because it does not appear to be built for this architecture. Is there a way to add this to the supported architectures?

    To reproduce:

    1. Add Resolver swift package to your project and import it into a file.
    2. Add "arm64" in the "Excluded Architectures" build setting for your project. (this is needed when 3rd party libraries have not yet been built for Apple Silicon)
    3. Attempt to build your application.
    opened by brentjensen 7
  • LazyInjected, WeakLazyInjected causing memory leak

    LazyInjected, WeakLazyInjected causing memory leak

    Simplified example:

    extension Resolver {
        static var context: Resolver!
    }
    
    class A { }
    
    class B {
        @LazyInjected(container: .context) var a: A
    }
    
    func assemble(container: Resolver) {
        container.register { A() }
        container.register { resolver -> B in
            Resolver.context = resolver
            defer { Resolver.context = nil }
            return B()
        }
        .scope(.container)
    }
    
    // Example viewcontroller
    
    import UIKit
    import Resolver
    
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            let container = Resolver(child: .root)
            assemble(container: container)
            let _ = container.resolve(B.self)
        }
    }
    

    Experienced: Memory leak occours due to the following circular references: container -> container.cache -> B -> B.@LazyInjected(container: .context) var a: A -> container Screenshot 2021-10-19 at 12 01 59

    Suggested fix: A possible way to resolve this is to mark the public var container: Resolver? property inside LazyInjected and WeakLazyInjected as weak.

    opened by ZsoltMolnarMBH 7
  • Hasahble's hashvalue is not promised to be unique and should not be used as a key in the registrations dictionary

    Hasahble's hashvalue is not promised to be unique and should not be used as a key in the registrations dictionary

    Resolver's registration dictionary uses ObjectIdentifier's hashValue as its keys, but hash values shouldn't be assumed to be unique. There's a risk of overwriting a value in the registrations dictionary.

    Note that for two arbitrary types SomeService and SomeOtherService,

    ObjectIdentifier(SomeService.self) != ObjectIdentifier(SomeOtherService.self) // true

    but

    ObjectIdentifier(SomeService.self).hashValue != ObjectIdentifier(SomeOtherService.self).hashValue // may be false

    The issue would manifest when registering services in the snippets below:

    public final class Resolver {
        @discardableResult
        public final func register<Service>(_ type: Service.Type = Service.self, name: Resolver.Name? = nil,
                                            factory: @escaping ResolverFactory<Service>) -> ResolverOptions<Service> {
            lock.lock()
            defer { lock.unlock() }
            let key = ObjectIdentifier(Service.self).hashValue  **<- extracted key is another Hashable's hashvalue; not unique**
            let registration = ResolverRegistrationOnly(resolver: self, key: key, name: name, factory: factory)
            add(registration: registration, with: key, name: name)
            return registration
        }
    
        ...
        private var registrations = [Int : [String : Any]]() *<- subject to overwriting values where hash values from upstream ObjectIdentifiers are the same*
    }
    

    Since ObjectIdentifier itself is Hashable, I'd propose using ObjectIdentifier itself as the key to the registrations dictionary, and not hashValues extracted from ObjectIdentfier objects.

    opened by jrogccom 7
  • Multi injection support

    Multi injection support

    Introduction

    After working with this library for a while, I noticed a shortcoming in it: Multi Injection Suppose we need an injectable array of a specific type, If this dependency is already registered somewhere as an array once, everything will work fine. But what if we want the elements of this array to be registered in different parts of the code? 

    Explain the scenario

    In this scenario we have an injectable array of interceptors in the NetworkManager class which is defined in our network layer:

    public protocol Interceptor {
      func intercept(_ request: Request) -> Response
    }
    
    public class NetworkManager {
     public var interceptors: [Interceptor]
    }
    

    We need to register various interceptors in different modules and we expect these interceptors to be injected as an array to our NetworkManager

    Resolver.register(multi: true) { SetAuthHeaderInterceptor() as Interceptor }
    Resolver.register(multi: true) { ActivityIndicatorInterceptor() as Interceptor }.scope(.application)
    
    public class NetworkManager {
     @MultiInjected public var interceptors: [Interceptor]
    }
    
    

    In such a case we should tell the DI to register our Service with a different strategy.

    Let me continue in the code...

    opened by iMattin 6
  • Could not cast value of type 'Swift.Optional<Swift.Optional<Any>>' to 'Swift.String'

    Could not cast value of type 'Swift.Optional>' to 'Swift.String'

    Sorry, my app crash and get this error.
    And this is my code.

    @objc protocol Animal {
        var name: String { get }
        @objc optional var running: Bool { get }
    }
    
    class Horse: Animal {
        let name: String
        let running: Bool
    
        init(name: String, running: Bool) {
            self.name = name
            self.running = running
        }
    
        convenience init(name: String) {
            self.init(name: name, running: false)
        }
    }
    
    class ResolverParameterInjectionVC: UIViewController, Resolving {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let smallHorse: Animal = Resolver.resolve(name: "small", args: "Spirit")
            let bigHorse: Animal = Resolver.resolve(name: "big", args: ["name": "Lucky", "running": true])        
        }
        
    }
    
    extension Resolver {
        public static func registerResolverParameterInjectionVC() {
            
            register { (_, args) in
                Horse(name: args.get())
            }.implements(Animal.self, name: "small")
        
            register { (_, args) in
                Horse(name: args("name"), running: args("running"))
            }.implements(Animal.self, name: "big")
        }
    }
    
    opened by Bruce9487 0
  • Ability to resolve multiple implementations of same type

    Ability to resolve multiple implementations of same type

    This PR adds the ability to resolve all registered implementations for a given type, where the implementations are differentiated by names. Please let me know what I can optimize.

    opened by kmaschke85 9
Releases(1.5.0)
  • 1.5.0(Oct 16, 2021)

    • Added .container scope that lives for the lifetime of a given Resolver container (PR#131)
    • Added init(child:) to replace deprecated init(parent:)
    • Updated ResolverRegistration to give ResolverOptions the ability to add new behaviors to a given registration (Based on PR#130)
    • Updates to ResolverRegistration so external services aren't poking around its internals
    • Removed deprecated scopes from Resolver base class.
    Source code(tar.gz)
    Source code(zip)
  • 1.4.5(Oct 5, 2021)

  • 1.4.4(Sep 6, 2021)

    • Reduced code size and improved performance
    • Update registration cache key mechanism to prevent possible registration overwrites
    • Minor documentation improvements, changes, typos, etc..
    Source code(tar.gz)
    Source code(zip)
  • 1.4.3(Jun 20, 2021)

  • 1.4.2(Jun 10, 2021)

    • Fix threading issue in LazyInjected and WeakLazyInjected property wrappers
    • Fix argument passing in .implements
    • Update project for Xcode 12.5
    • Update Swift class deprecation
    Source code(tar.gz)
    Source code(zip)
  • 1.4.1(Feb 26, 2021)

  • 1.4.0(Jan 10, 2021)

    • Updated registration and resolution lock/unlock stratagies for better thread safety and performance.
    • Updated automatic service registration code for better performance and to use new locking mechanisms.
    • Moved default scopes from Resolver to ResolverScope for better autocompletion.
    • Added .name(fromString:) to Resolver.Name to handle passing string variables.
    Source code(tar.gz)
    Source code(zip)
  • 1.3.0(Jan 6, 2021)

    • Add Name spaces - Based on PR#83
    • Fix protocol issue in WeakLazyInjected property wrapper
    • Update Resolver.reset to also reset known caches.
    Source code(tar.gz)
    Source code(zip)
  • 1.2.1(Dec 7, 2020)

    1.2.1

    • Fix autorelease bug in WeakLazyInjected property wrapper

    1.2

    • Rewrite argument passing code to support multiple arguments
    • Add @WeakLazyInjected property wrapper
    • Update Cyclic Dependencies and Annotation documentation
    • Fix to build on Linux - PR#75
    • Fix to build dynamically using SPM - PR#71
    • Fix issue with hierarchical containers - PR#50
    • Add tvOS deployment target for Cocoapods - PR#56
    Source code(tar.gz)
    Source code(zip)
  • 1.1.4(May 2, 2020)

  • 1.1.3(May 2, 2020)

    • Add @OptionalInjected property wrapper
    • Fixed Empty CURRENT_PROJECT_VERSION variable.
    • Mark framework to use application extension safe api only
    • Use fatalError message instead of print
    Source code(tar.gz)
    Source code(zip)
  • 1.1.2(Dec 29, 2019)

    • Add @InjectedObject property wrapper for SwiftUI support
    • Ensure proper initialization of pthread mutexes
    • Add missing public keyword to registerAllServices() method
    • Expand supported platforms to include macOS, tvOS, and watchOS
    • Set minimum supported platform on iOS to iOS 11
    Source code(tar.gz)
    Source code(zip)
  • 1.1.1(Dec 2, 2019)

    • Update project for Swift 5.1 and SPM
    • Add @Injected property wrapper for Swift 5.1
    • Add @LazyInjected property wrapper for Swift 5.1
    • Revise unit tests for more code coverage
    • Make static registration function public and add concurrency mutexes
    • Add type specification to defaultScope
    • Fix initializers on scope types to allow public instantiation.
    • Allow clearing the shared cache
    Source code(tar.gz)
    Source code(zip)
  • 1.0.7(Feb 18, 2019)

  • 1.0.6(Feb 18, 2019)

  • 1.0.5(Jun 9, 2018)

  • 1.0.3(May 6, 2018)

  • 1.0.1(Apr 3, 2018)

  • 0.0.1(Mar 29, 2018)

Owner
Michael Long
I'm a technology consultant and specialist in mobile application development, web development, software engineering, and design.
Michael Long
Injection - Dependency injection using property wrappers

Dependency injection using property wrappers. Registering types: // injecting a

Alejandro Ramirez 3 Mar 14, 2022
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
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
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