Giter VIP home page Giter VIP logo

resolver's Introduction

Resolver icon

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

Note: Resolver is now officially deprecated and replaced by my new dependency injection system, Factory. Factory is a true container-based dependency injection system that's compile-time safe and is smaller, lighter, and faster than Resolver. As good as Resolver is, Factory is better.

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.

There's also an @InjectedObject wrapper that can inject Observable Objects in SwiftUI views.

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

  • Resolver 1.5 updated several of the registration and caching mechanisms used within Resolver. This one probably isn't an issue unless you wrote something that depended upon Resolver's internal behavior.

Sponsor Resolver

If you want to support my work on Factory and Resolver, consider a GitHub Sponsorship! Many levels exist for increased support and even for mentorship and company training.

Or you can just buy me a cup of coffee!

Author

Resolver is designed, implemented, documented, and maintained by Michael Long, a Lead iOS Software Engineer and a Top 1,000 Technology Writer on Medium.

Michael was also one of Google's Open Source Peer Reward winners in 2021 for his work on Resolver.

License

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

Additional Resouces

resolver's People

Contributors

andi357 avatar bensline avatar beretis avatar faimin avatar hmlongco avatar joehinkle11 avatar johnrnyquist avatar li-bot avatar mcfans avatar michaelbutler1998 avatar mojtabahs avatar ralfebert avatar rebekkaorth avatar ruslanvs avatar sdhzwm avatar stuclift avatar super-ultra avatar ultraon avatar vitusortner avatar woookdev avatar zsoltmolnarmbh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

resolver's Issues

Unknown attribute 'Injected'

I checked that Swift 5 is correctly selected in my target and in pods.

I have correctly configure the development the version in my Podfile:

	pod 'Resolver', :git => 'https://github.com/hmlongco/Resolver', :branch => 'develop'

image

still I get :

Unknown attribute 'Injected'

[Add] iOS 10 support

[Add] iOS 10 support for Cocoapods & SPM
PR: #46

Reason: Many company still support iOS 10 users

Solution:

  • Cocoapods: Seperate into 2 subspecs - Main & SwiftUI
  • SPM: The default Resolver lib contain only the Resolver.swift, while the ResolverSwiftUI lib will contain Resolver+SwiftUI.swift.

Usage:
For developer who wish to support iOS 10 users

  • Cocoapods: pod 'Resolver/Main
  • SPM: import only Resolver package. Ignore ResolverSwiftUI package

For those who fancy SwiftUI:

  • Cocoapods: pod 'Resolver'
  • SPM: import both Resolver & ResolverSwiftUI package

Please discuss further information with me here.
By the way, thanks so much for your superb library.

Cocoapods multiple platform project

Can you add tvOS support to the podspec? Currently i added Resolver by swift packages, i would be grateful, if it could be done by cocapods.

How to inject different types of doubles?

Hi there,
First of all, thank you for making this library available to the community.

Going directly to my question, thereโ€™s some well-know way for injecting different types of test doubles for a given dependency?

In a test project, I was able to inject a spy using the Swift Compiler Custom Flags approach, but thereโ€™re some cases that weโ€™d like to inject a stub instead. How we could do that?

Having a double that behaves like a stub and a spy at the same time is an option, but I'm wondering if there is a way to avoid that.

Hereโ€™s how my code is looking like now:

extension Resolver {
    
    static func registerMenuService() {
        let menuService: MenuService
        
        #if MOCK
            menuService = MenuServiceSpy()
        #else
            menuService = MenuGravyService()
        #endif
        
        register {
            menuService
        }
    }
}

class ViewControllerTests: XCTestCase {
    
    func testViewController_whenLoadsView_fetchesRestaurantData() throws {
        let viewController = ViewController()
        
        viewController.viewDidLoad()
        
        let menuServiceSpy = try XCTUnwrap(viewController.service as? MenuServiceSpy)

        XCTAssertTrue(menuServiceSpy.didFetchRestaurant)
    }
}

MacOS support

Hello there,

First of all this is great library, thanks for making it available.

Do you have plans to bring support for MacOS targets? :)

That would be awesome, as MacOS now will gain a lot of tractions due to the latest apple developments and Im also trying to port my SwiftUI iOS app to MacOS with no luck due to Resolver being available only for iOS at the moment.

Best,
Socratis

Returning a single instance

Is there a way to register a dependency so that the same instance is injected every time? Without using a singleton that is.

Safe named dependencies

It would be nice to let the compiler assist with named dependencies.
I propose to implement something similar to Notification.Name.
So the public API will be:

extension Resolver.ServiceName {
    static let myServiceName = Self("my.service")
}

Resolver.resolve(MyService.self, name: .myServiceName)

@hmlongco what do you think about that? I'm going to implement this for our needs soon, so looking forward to your feedback.

Swift package product 'Resolver' is linked as static library by 'XXXX' and 'YYYY'.

I have a project with few subprojects and while trying to add Resolver via SPM to both projects, getting this error:

--- Swift package product 'Resolver' is linked as static library by 'XXXX' and 'YYYY'. This will result in duplication of code ---

Do you think it makes sense to add

type: .dynamic

to .library section in Package.swift file?

I know it is not really a Resolver issue, since there is ongoing thread here:
https://developer.apple.com/forums/thread/128806

where people discussing this issue (and as usual - without any help from Apple), but I think at least Resolve can fix it on its side.

ViewModels as scope shared still keeps its values.

I came across this closed issue response and I thought alright that is the way I should go when dealing with StateObject types in SwiftUI. I then started running into another issue. If I keep the scope(shared) then all of the data dealing with that view model keeps being retained. What I am looking for is away to get passed the StateObject issue and have the view model init when the view is also init.

Should also note that to use it properly with SwiftUI views you probably want your view models to be .scope(shared)

Also, thanks for using Resolver.

_Originally posted by @hmlongco in https://github.com/hmlongco/Resolver/issues/68#issuecomment-687679447_

Can't change defaultScope

Because the defaultScope property has no explicit type definition, it's implicitly typed as the default value, which is ResolverScopeGraph. This makes it impossible to set it to a different scope, e.g. Resolver.application as this causes the following error: Cannot assign value of type 'ResolverScopeApplication' to type 'ResolverScopeGraph'.

To fix this issue, defaultScope should be typed as ResolverScope.

Thanks for this otherwise great little lib, and the accompanying Medium post, which brought me to using this along with the property wrapper :)

Build VIPER module

Hi @hmlongco, I have a problem and I can't find the solution, so, I have the VIPER module, for example:

protocol ViewInput: class {
  func configure()
}

protocol ViewOutput: class {
  func viewDidLoad()
}

class ViewController: UIViewController, ViewInput {
  /// Reference to the Presenter.
  var output: ViewOutput!

  func viewDidLoad() {
    output.viewDidLoad()
  }

  func configure() {
    // code ...
  }
}

class Presenter {
  /// Reference to the view controller.
  weak var view: ViewInput?
}

extension Presenter: ViewOutput {
  func viewDidLoad() {
    view.configure()
  }
}

My registration:

register { Presenter() } .resolveProperties { (resolver, presenter) in
  presenter.view = resolver.resolve(ViewController.self)
}
  
register { ViewController() }.resolveProperties { (resolver, viewController) in
  viewController.output = resolver.resolve(Presenter.self)
}

And I have the error:

Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee6bc4f28)

Best practices: multiple application flows

Let's say you have an application following the MVVM+C architecture, what are the best practices to implement DI with resolver in the following scenario:

  1. There's a root coordinator with some dependencies.

  2. Child coordinators need to be passed some of the dependencies from the root coordinator.

  3. A particular coordinator

I've been reading the documentation and I think I should be leveraging multiple containers + different scopes but the big picture is still not very clear.

Mainly, how can I share dependencies between the root coordinator and each child, and further add different dependencies for each child coordinator without my code becoming hundreds of repeated registration lines (root coordinator passing dependencies to the child coordinator and then registering their own)

And a bonus question, how can I inject a dependency that is dynamically dependent on a parameter? Let's say, for example, a view model with a specific model id.

Thanks!

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]

[Help] Override class in UI Tests

Hi,

It's seem that it is not possible to override some class in unitTest or UITest target;

For unit test, I can workaround this by removing the hosted app, but how can I do that for UITest without adding some ENV variable to the main application.

Main app :

extension Resolver: ResolverRegistering {
    public static func registerAllServices() {
        register { PersonAPI() as PersonAPIProtocol }.scope(application)
    }
}

UITest target :

let tu = Resolver(parent: .main)
tu.register { TUPersonAPI() as PersonAPIProtocol }.scope(Resolver.application)
Resolver.root = tu
let api = Resolver.resolve(PersonAPIProtocol.self, name: nil, args: nil)
///  api is TUPersonAPI instance => OK  
let app = XCUIApplication()
app.launch()
class ViewModel {
    @Injected var api: PersonAPIProtocol
    
    func fetch(completion: @escaping ([Person]) -> ()) {
        
        self.api.fetchPersons { (persons) in
            completion(persons)
        }
    }
}

But when launching UI test api is an PersonAPI instance in viewModel

Do apps and tests have a completely different context ?
Ben

Multiple Arguments

So I'm thinking about providing support for multiple arguments in Resolver. I'm not a big fan of the concept but several people have asked for it.

Currently, you can pass in a single argument of type Any? that you then unpack from the second argument in the registration factory.

register { (_, arg) -> SomeService in
            let mode: Int = arg as! Int
            SomeService(mode)
        }

let service: SomeService = resolver.resolve(args: true)

Now, you can pass multiple arguments today by creating a custom struct or throwing items into a dictionary, but unpacking the Any? type in the factory is clunky.

With multiple argument support baked directly into Resolver, you basically provide more than one argument in a positional format.

let service: XYZEditService = resolver.resolve(arg0: true, arg1: "Fred")

The issue lies in consuming those arguments in the registration factory. With multiple argument support, I'm basically creating a struct that you can easily unpack via subscript to get at the typed arguments.

The first (and easiest) approach would simply require the user to "create" the new structure from the existing argument parameter if they want or need multiple arguments.

register { (_, args) -> XYZEditService in
            let args = ResolverArgs(args)
            return XYZEditService(editing: args[0]!, name: args[1]!)
        }

Again, this would be easiest to support, but seems a bit clunky in that in order to use it you always have to translate args into ResolverArgs. On the plus side, this mechanism doesn't break existing code, but on the flip side with multiline factory functions you often end up having to explicitly specify the factory return type. Another minus.

The second approach would be to add another factory variant in which I could simply create and pass ResolverArgs directly. To use it you'd just "skip" the old set of args and access the new set of multiple arguments.

Note in the following example the new "args" are now passed as the third parameter slot, not the second as before.

register { (_, _, args) in
        XYZEditService(editing: args[0]!, name: args[1]!)
        }

It's quite a bit cleaner, but for some reason having two different argument parameters bugs me.

The major benefit is that again, it doesn't break existing registration factories that might be using the existing arguments parameter. One other negative is that the code for this is a bit more complex.

So, as the final solution I could simply replace the old arguments parameter with the new one and be done with it.

register { (_, args) in
        XYZEditService(editing: args[0]!, name: args[1]!)
        }

In other words, this is how both single and multiple argument parameter passing work in Resolver from this point forward. Pass in arguments, and they're provided to the factory as ResolverArgs. Period.

The major drawback being, of course, that this change breaks existing code.

Now, I can promote the old "args" argument automatically and make it available as the first argument in the new array, but you'd still need to change the code to access it...

register { (_, args) in
            let mode: Int = args[0]!
            SomeService(mode)
        }

All in all, I'm definitely leaning towards the later approach, but again, it has the drawback of breaking existing code.

So... any thoughts? Is a clean feature desirable enough to warrant breaking existing code? At what point do you simply bite the bullet and move forward?

`Reset` not resetting Application-scope objects

Issue:
Upon calling Resolver.reset(), registerAllServices() is called, however, upon checking the data of an Application-scope object, the data has not been reset

Repro steps:

  1. Register class as Application scope
  2. Set bool in class to some new value
  3. Call Resolver.reset()
  4. Force registerAllServices() to get called again
  5. Check data in application-scope class

Expectation:
Bool is set back to original value

Actual:
Bool has not changed.

Unsure if this expectation is correct but would appreciate some sort of resolution

ResolverRegistering registration vs local registration question

I was a little surprised that if I register something "globally" via implementing ResolverRegistering and then register it again locally, the global registration still wins. I proved that with a test that demonstrates the ordering.

My question is if this is correct or if I'm thinking about the ordering here the wrong way or there is a better way to do this. For context I'm trying to figure out the best way to go about registering a different set of implementations for a test target. I'd like to be able to do something like registering production implementations in my main target using ResolverRegistering and then register mock implementations in my test target by overriding the registration by calling something like Resolver.register(TestImplementation()).implements(Protocol.self).

As an aside, I also wonder if you have thoughts around a different approach to resolving implementations in a test target. What if something like a ResolverOverrideRegistering protocol existed that could be implemented in a test target only that would then be called after ResolverRegistering was checked to override any implementations needed for testing? Because in my current project I don't have a good way to even hook into local overrides as described above and would prefer to do so globally.

Thanks for the nice library.

CFBundleVersion is missing - AppStore delivery

When I try to upload an app to AppStore which uses Resolver framework, I get ITMS error 90056.

"This bundle Payload/some.app/Frameworks/Resolver.framework is invalid. The Info.plist file is missing the required key: CFBundleVersion."

I checked Info.plist and there is "CFBundleVersion: $(CURRENT_PROJECT_VERSION)", then I checked "CURRENT_PROJECT_VERSION" variable in build settings and it is empty.

As soon as you fill the field, the error goes away.

Resolver and Texture when doing Viper modular

I have two questions:

  1. am I using resolver correctly to build VIPER modules
  2. why is it working when using UIKit (UIviewController) and not when using AsyncDisplayKit/Texture
    (ASViewController<ASDisplayNode>)

1) Am I using resolver correctly

I have multiple module (here Root and Signin).
Each module are in there own *.xcodeproj.

Here are my files, with UIKit (working โœ…):

(Public) Module Root
import UIKit
import Resolver

public class RootModule {
    @LazyInjected var view: RootView

    public init() {}

    public func getViewController() -> UIViewController {
        view
    }
}
(Internal) RootView
import Resolver
import UIKit

final class RootView: UIViewController {
    @Injected var presenter: RootPresenter

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        presenter.viewDidAppear()
    }
}
(Internal) RootInteractor
final class RootInteractor {
    weak var presenter: RootPresenter!
}
(Internal) RootPresenter
import Resolver

final class RootPresenter {
    @Injected var router: RootRouter
    @Injected var interactor: RootInteractor
    weak var view: RootView!

    func viewDidAppear() {
        router.showSignIn()
    }
}
(Internal) RootRouter
import Resolver

import SignIn

final class RootRouter {
    @Injected var signInModule: SignInModule

    weak var presenter: RootPresenter!

    func showSignIn() {
        let siginInView = signInModule.getPresentation()
        siginInView.modalPresentationStyle = .fullScreen

        let rootView = presenter.view

        rootView?.present(siginInView, animated: true, completion: nil)
    }
}

I'm setuping DI like this:

import Resolver

extension Resolver {
    public static func registerRoot() {
        register { RootModule() }.scope(application)
        register { RootView() }.resolveProperties { _, view in
            view.presenter.view = view
        }
        register { RootInteractor() }
        register { RootRouter() }
        register { RootPresenter() }.resolveProperties { _, presenter in
            presenter.router.presenter = presenter
            presenter.interactor.presenter = presenter
        }
    }
}

Few questions:

  1. Is the registration correct? There shouldn't be any dependency cycle nor retain cycle?
  2. Who should hold the view? Is it correct if the RootModule is holding a lazy view?
  3. Does the order in the registerRoot() change something?

# 2) why is it working when using UIKit and not AsyncDisplayKit

Before EDIT

Doing the following changes (and only this changes):

import Resolver
-import UIKit
+import AsyncDisplayKit

-final class RootView: UIViewController {
+final class RootView: ASViewController<ASDisplayNode> {
    @Injected var presenter: RootPresenter

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        presenter.viewDidAppear()
    }
}

makes:
image

when looking at the callstack I can see:
image

What does that means ? any idea why there is a difference between ASViewController<ASDisplayNode> and UIViewController?

EDIT: This solves it

import Resolver
import AsyncDisplayKit

final class RootView: ASViewController<ASDisplayNode> {
    @Injected var presenter: RootPresenter

+   init() {
+       super.init(node: ASDisplayNode())
+   }

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

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        presenter.viewDidAppear()
    }
}

Implement Resolver across a multi-module application?

First, thanks for Resolver -- it's been a great service for me. But now I'm refactoring my project from a monolith to a modular model where all the modules are frameworks, except for the root application module.

I haven't been able to make Resolver work across more than one module, and I've tried various strategies -- putting an AppDelegate+Injection inside each module, using one AppDelegate+Injection file and importing modules with individual +Injection files, importing a module and registering its classes inside the host module. It doesn't matter whether I'm working in a framework module or the application module -- anything that crosses the boundaries of a single module breaks with a service not resolved error.

Are there any plans to implement Resolver across modules, or am I just doing it wrong?

registerAllServices method is not called in the main project

Setup

I have an iOS application target which uses Resolver 1.4.0. I also have a cocoa touch framework target using Resolver too. The framework is embedded in the main application's bundle. The Resolver framework is pulled into the project via Cocoapods, and it is shared through an abstract target in the Podfile.

Resolver registrations

Both targets have their own Resolver extensions for registering the dependencies.

import Resolver

extension Resolver: ResolverRegistering {
    public static func registerAllServices() {
        ...
    }
}

The problem

The compiler can compile both targets, the application's bundle is created successfully and looks good. A strange behaviour happens only on iOS 12.X simulators (on real devices, and on simulators with later iOS versions everything is working fine): when the application is started the registerAllServices static method is called from the framework and not from the main application target (even if the framework is not imported in the code), so the application won't register its classes and it crashes during the first injection.

References related to problem:

Sample project

ResolverExample.zip

Question

Do you have any options / ideas how to handle this case?

Spelling issue

In Resolver.swift, there is a spelling issue on line 221.

Source code:
fatalError("RESOLVER: '\(Service.self):\(name ?? "")' not resolved. To disambiguate optionals use resover.optional().")

The issue:
resover.optional() -> resolver.optional()

Spell-corrected code:
fatalError("RESOLVER: '\(Service.self):\(name ?? "")' not resolved. To disambiguate optionals use resolver.optional().")

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?

[HELP] Different Resolver in production, mock, testing, etc..

Hi,

New player with Resolver, I wonder what is the right solution to have different injectors depending on execution context.

In release, I plan to use what is recommended:

Resolver: ResolverRegistering

For my unit tests I need to inject different classes.

Ex:
Production:

register { AccountsAPI() as AccounstAPIProtocol }.scope(application)

Unit test:

register { MockAccountsAPI() as AccountsAPIProtocol }.scope(application)

When performing unit tests or UI tests, the idea is not to inject the classes used in production.

How would you do that?

SwiftUI application with no AppDelegate or SceneDelegate

How can I get Resolver to work when there isn't an AppDelegate or SceneDelegate?

@main
struct HopApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}
extension Resolver: ResolverRegistering {
    public static func registerAllServices() {
        registerServices()
    }
}

I have the registerAllService implemented but it doesn't get called.

Reset cache issue

If you add an item to the cached scope:
resolver.register { XYZValue() }.scope(Resolver.cached)
And then call:
Resolver.cached.reset()
The item is still returned when calling:
let newService: XYZValue = resolver.optional()

I would expect that the item is not returned in the last line, due to having reset the cache.

Register more than one dependency for a given protocol

Lets say I have a class that needs a list of dependencies conforming to Loggeable, how do I go about injecting these dependencies if they all conform to the same protocol? What I found is that the last registration overwrites all other

Customization of a Service which is registered via .implements()

Hey there,
thanks for making Resolver available to all of us!

I just ran into a problem when trying something like this

main.register { MyService() }.implements(MyProtocol.self).scope(customScope)

It took me quite a while to figure out that the registration for MyProtocol is created using the default scope rather than my customScope. This is because .implements() returns the original registration rather than the one it just created. This probably makes sense if you chain it with other things but it is not really obvious from the outside when using the .implements() call.
Also what would be the best way to customize my .implements() registrations?

Published values from injected services getting lost

I am currently facing the problem that my nested SwiftUI Views are not getting the latest values published by my Injected Service. I would like to describe my problem using a highly simplified representation of my current project structure to ensure traceability.

extension Resolver: ResolverRegistering {
    public static func registerAllServices() {
        register { Service() }.scope(.shared)
    }
}
final class Service {
    @Published var foo: String? = "bar"
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        // I subscribe to some async data here
        someRepository.$dataStream
            .sink { [weak self] data in
                // "newValue" or nil should get assigned to foo here
                self?.foo = data?.foo
            }
            .store(in: &cancellables)
    }
}
final class MainViewModel: ObservableObject {
    @Injected var service: Service
    
    @Published var someCondition = false
    
    private var cancellables = Set<AnyCancellable>()

    init() {
        service.$foo
            .sink { [weak self] foo in
                // receives "newValue"
                print(foo)
                
                self?.someCondition = true
            }
            .store(in: &cancellables)
    }
}

struct MainView: View {
    @StateObject var mainViewModel = MainViewModel()

    var body: some View {
        ZStack {
            if someCondition {
                ChildView()
            }
        }
    }
}
final class ChildViewModel: ObservableObject {
    @Injected var service: Service
    
    @Published var foo: String?
    
    private var cancellables = Set<AnyCancellable>()

    init() {
        service.$foo
            .sink { [weak self] foo in
                // foo still equals to "bar" here but it should be "newValue"
                self?.foo = foo
            }
            .store(in: &cancellables)
    }
}

struct ChildView: View {
    @StateObject var childViewModel = ChildViewModel()
    
    var body: some View {
        ZStack {
            Text(childViewModel.foo)
        }
    }
}

Perhaps there is also a relation to this post on SO.
Please lmk if you need more details.

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.

`registrations` is not thread safe

image

I reckon it is becaue the variable registrations of Resolver.swift is not thread-safe and there is no Mutex when write/read it.
Imaging register and resolve are called simultaneously from different threads, data race issue might happen.

private var registrations = [Int : [String : Any]]()

//...
public final func register<Service>(_ type: Service.Type = Service.self, name: String? = nil,
                                        factory: @escaping ResolverFactoryArguments<Service>) -> ResolverOptions<Service> {
        let key = ObjectIdentifier(Service.self).hashValue
        let registration = ResolverRegistration(resolver: self, key: key, name: name, factory: factory)
        if var container = registrations[key] {
            container[name ?? NONAME] = registration
            registrations[key] = container
        } else {
            registrations[key] = [name ?? NONAME : registration]
        }
        return registration
    }

Fatal Error: FIRFirestore not resolving

Hi!

I'm having a problem using Resolver in my iOS + Firebase project. When I run my app, I get the following error:

Fatal error: RESOLVER: 'FIRFirestore:' not resolved. To disambiguate optionals use resolver.optional().: file Resolver/Resolver.swift, line 222.

I've read your docs on optionals, and I don't have any in my project that relate to the services I've registered:

Screen Shot 2021-01-18 at 9 30 35 PM

Screen Shot 2021-01-18 at 9 31 25 PM

Screen Shot 2021-01-18 at 9 31 34 PM

For reference, I've been following this wonderful tutorial by someone who works at Firebase: https://peterfriese.dev/replicating-reminder-swiftui-firebase-part2/

I'm not sure if I'm just misunderstanding the way injected variables work, or how DI files are resolved behind the scenes. Any help would be greatly appreciate!

Cheers,

Harrison Ferrone

defaultScope has erroneous type ResolverScopeGraph

extension Resolver: ResolverRegistering {
    static func registerAllServices() {
        Resolver.defaultScope = Resolver.unique
        registerMyNetworkServices()
    }
}

Cannot assign value of type 'ResolverScopeUnique' to type 'ResolverScopeGraph'

Example project

Could you add some example project how to use this library?
Everything is clear by reading docs, but with example app it will be more simple, I think.

ResolverRegistration.cacheKey should include resolver hash?

I wonder if based on your description of containers, a container should have its own cached instances of an application scope. Consider an example:

extension Resolver  {
    static let resolver1 = Resolver(parent: main)
    static let resolver2 = Resolver(parent: main)
    static func registerAll() {
        resolver1.register { UIStoryboard(name: "storyboard1", bundle: nil) }.scope(application)
        resolver2.register {
            // 2
            UIStoryboard(name: "storyboard2", bundle: nil)
        }.scope(application)
        resolver1.resolve() as UIStoryboard
        register { ((resolver2.resolve() as UIStoryboard).instantiateInitialViewController() as! VC2) }
    }
}

2 will never get executed unless we provide each Resolver instance with its own ResolverScopeApplication instance. Same thing will happen in your Containers example if the registrations will have the application scope. Is it intended design?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.