Giter VIP home page Giter VIP logo

dip's Introduction

Hi there πŸ‘‹

I am an iOS Infrastructure & Platform Engineer (aka Mobile DevOps).
I provide developer with tools, scripts and automation to improve their Developer Experience

About my contributions

About my expertise

I create and provide tools to iOS developers to improve their productivity. This includes:

  • πŸ€– Automating the release process to QA, TestFlight, AppStore…
  • πŸ‘₯ Improving the PR review experience (using Danger and Vapor Bots to interact with your PRs, among others)
  • ⏱ Improving the iOS CIs pipelines and build times
  • βš™οΈ Providing integration between tools for developers, QA & product (GitHub, JIRA, Lokalize, Figma, TestRails, CI, …)
  • πŸ“‹ Consulting on testing strategies (project testability, balance between Unit/UI/Snapshot/manual tests, interaction with QA…)
  • ✨ And much more πŸ™‚

Talking with me


Learn more about me on the About page of my blog

dip's People

Contributors

alisoftware avatar bvirlet avatar dchohfi avatar dentelezhkin avatar gavrix avatar gruppio avatar ilyapuchka avatar john-twigg-ck avatar kandelvijaya avatar michalsrutek avatar phatblat avatar pr0ger avatar tapsandswipes avatar trimmurrti avatar yikandel 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

dip's Issues

Investigate possible retain cycles passing container in factory/resolveDependenciesBlock

As @AliSoftware pointed out in #22 discussion there could be some retain cycles between container and definitions. Will investigate this possibility in this issue.


Discussion from #22 :

AliSoftware:

Not related to this PR I know, but now that I read that again I'm wondering: shouldn't we use [unowned container](or at least [weak container]) here? It seems to me that container strongly reference resolveDependenciesBlock which itself strongly references container, hence a strong reference cycle, right?

ilyapuchka:

I don't think so, cause here we don't capture container from outer context, we pass it as an argument at runtime. So the reference should be released as soon as block returns. I suppose...

Speaking of unowned I tried to use factory block without [unowned container] it (like in line 93) and it still looks like there is no retain cycle... I test it by having a strong reference to container and a weak reference. When I release a strong reference weak also is being released. That would not happen in retain cycle... but if I think of it there should be one if there is no [unowned container] or [weak container], cause container holds definitions and they hold their factories... Maybe it's just too late at night to think about that πŸ˜ͺ πŸŒƒ

AliSoftware:

I don't think so, cause here we don't capture container from outer context, we pass it as an argument at runtime. So the reference should be released as soon as block returns.
Don't we? I mean, container captures resolveDependenciesBlock right? So unless that block don't use the container argument in the closure, this container is captured automatically by the closure and not "released as soon as the block returns" (as the resolveDependenciesBlock will still be captured and kept as a property in the container). So this captured container will only be released when the block itself is released (set back to nil).

(If the block were not stored as a strong property by the container but used right away, we would be able to mark it @NoEscape, which is not the case here)

We could use a simpler Sample project

I feel like the current Sample Project grow a bit too much and went probably too far and complicated, because I wanted to make it closer to a real-world project but as a result the usage of Dip is probably not enough in the center of the Sample anymore.

We probably could have a simpler Sample project which would concentrate on Dip itself, be less complex and avoid any other complexities or wandering.

Interception for NSObject subclasses

That actually has two sides:
1. Using Dip from Objective-C code. That will require adding register/resolve apis that will accept type as parameter (#75)
2. Providing some advanced features that require more flexible runtime and will work only for NSObjects (like interception using NSProxy, anything else?)

If something of that should be implemented it will make sense to do it in a separate repo and maybe also create Dip org and gather there all repos. Also would let us to extract simple app to it's own repo, living only playground here.

Using tags seems to create a separate scope.

I have a bunch of plugin-type objects that all need implement the same protocol and I need to be able to resolve them by name when they are needed. When I saw the Named Components, I thought, great, this is exactly what I need.

I ended up running into an issue though then those plugins have shared dependencies.
It appears that when an object is resolved with a tag, it creates a new scope for all of the dependencies.

Heres a simplified example

import Dip

var count = 0
class Dependency {
    let id: Int
    init() {
        self.id = count++
    }
}

class Root {
    let dep: Dependency
    init(dep: Dependency) {
        self.dep = dep
    }
}

let container = DependencyContainer()
container.register(.Singleton) {Dependency()}

container.register(.Singleton) {Root(dep: $0)}
container.register(.Singleton, tag: "other") {Root(dep: $0)}

let dep = try! container.resolve() as Dependency
let noTag = try! container.resolve() as Root
let withTag = try! container.resolve(tag: "other") as Root

let rawId     = dep.id
let noTagId   = noTag.dep.id
let withTagId = withTag.dep.id

Since Dependency is marked as a .Singleton I would expect that there would only be one instance of it created in the container. What I found is that the instance that is injected into withTag is different than the one that is injected into the one resolved without the tag.

In the example above

rawId == 0
noTagId == 0
withTagId == 1

This was all ran in the Playground on the swift2.3 branch.
I'll look into the code, but I figured that I'd post it here in case anyone else has noticed this.

Change DefinitionOf generic parameters

Currently definition is defined as DefinitionOf<T, F> where T is an abstract type and F is factory type. With that there is no way to ensure that F actually returns T on the level of definition (it is ensured by container's register methods). Probably it would be more flexible to use DefinitionOf<T, U> where U is a type of parameters, then define typealias F = U throws -> T. Also definition key should be built only with arguments type.
Then we can separate parameters from factory type and maybe do something interesting with them (for example pass them to resolveDependencies block?). But that looks like a breaking change that does not bring any value by its own.

Could not cast Dip.WeakBox<Any> to ... if using .weakSingleton in separate module

@ilyapuchka
I found another frustrating issue, here the module definition:

let editUserEmailModule = DependencyContainer() { container in
  // EditUserEmailWireframe
  let editUserEmailWireframe = container.register(.weakSingleton) {
    EditUserEmailWireframe()
      as EditUserEmailWireframe
  }

  // EditUserEmailInteractor
  let editUserEmailInteractor = container.register(.weakSingleton) {
    EditUserEmailInteractor()
      as EditUserEmailInteractorInput
  }

  // ConfirmUserEmailInteractor
  let confirmUserEmailInteractor = container.register(.weakSingleton) {
    ConfirmUserEmailInteractor()
      as ConfirmUserEmailInteractorInput
  }

  // EditUserEmailViewPresenter
  let editUserEmailViewPresenter = container.register(.weakSingleton) {
    EditUserEmailViewPresenter()
      as EditUserEmailViewPresenter
  }
  container.register(editUserEmailViewPresenter, type: EditUserEmailModuleInterface.self)
  container.register(editUserEmailViewPresenter, type: EditUserEmailInteractorOutput.self)
  container.register(editUserEmailViewPresenter, type: ConfirmUserEmailInteractorOutput.self)

  // EditUserEmailViewController
  let editUserEmailViewController = container.register(.weakSingleton) {
    EditUserEmailViewController(nibName: "EditUserEmailViewController", bundle: nil)
      as EditUserEmailViewController
  }
  container.register(editUserEmailViewController, type: EditUserEmailViewInterface.self)

  // ConfirmUserEmailViewController
  let confirmUserEmailViewController = container.register(.weakSingleton) {
    ConfirmUserEmailViewController(nibName: "ConfirmUserEmailViewController", bundle: nil)
      as ConfirmUserEmailViewController
  }
  container.register(confirmUserEmailViewController, type: ConfirmUserEmailViewInterface.self)
}

let dipRoot = DependencyContainer() { root in
  ...

  mainModule.collaborate(
    with: root, editUserEmailModule
  )
  editUserEmailModule.collaborate(
    with: root, mainModule
  )
}

// Later in code
let controller = try? editUserEmailModule.resolve() as EditUserEmailViewController

I'm receiving the error:

Reusing previously resolved instance Dip.WeakBox<Any>
Could not cast value of type 'Dip.WeakBox<Any>' (0x1223b31d0) to 'MobitrackClientApp.EditUserEmailViewController' (0x10bd79020).

If I'll change .weakSingleton to .shared, crash be gone, but code will start to recreate some modules instead of reusing. Maybe I don't understand clearly how collaboration and scopes works together.

dip_could_not_cast

Port tests to Linux

#42 ported only thread safety tests. Need to port all other tests too. Will probably need to refactor a bit existing tests to make them compatible. With that Dip will be "Linux ready".

Pass containing instance in didInject block

Auto injecting wrappers Injected and InjectedWeak currently have a block that will be called when wrapped instance is injected by container. The variable that holds reference to wrapper is stored, so it is impossible to reference self when initializing this variable with initial value:

private let _service = Injected<Service> { service in
  self.service = _service //error
}
private(set) var service: Service!

It makes this block not so powerful as didSet property observer.
But we can pass an instance that holds the variable as block argument:

private let _service = Injected<Service> { (me: SomeClass, service) in 
  me.service = _service //me is a reference to self
}
private(set) var service: Service!

This way users can operate on other instance variables or call instance methods easily. We can use generic argument for type of me, but we can not ensure compile time type-safety.
That will be a breaking change.

Add Unit Tests to Dip itself

The Xcode project contains Unit Tests, but those are Unit Tests for the sample app, not Unit Tests to ensure Dip doesn't break.

Make first runtime argument parameter named

With introduction of runtime arguments (#8) the API became a bit confusing cause of mixing tags and arguments values. It would be better to use something like this:

container.register(tag: "tag") { (arg1: Arg1) in ... as Protocol }
container.resolve(tag: "tag", withArguments: arg1) as Protocol

To release this feature faster and without bumping major version (what we are going to do to release circular dependencies) we can leave old version and later (or can we do it right away?) deprecate it.

Resolve singleton to multiply interfaces?

Is it possible to use singleton to resolve different interfaces

class ServiceImp {}
extension ServiceImp : SomeService {}
extension ServiceImp : AnotherService {}

let container = ...
container.register(.Singleton) { ServiceImp() as SomeService }
container.register(.Singleton) { ServiceImp() as AnotherService }

Probably we could use next:

let service = ServiceImp()
container.register(.Singleton) { service as SomeService }
container.register(.Singleton) { service as AnotherService }

But does it makes sense?

Throwing errors instead of fatalError

Probably we should consider throwing errors in resolve instead of using fatalError. For now we use it only in one place, but there could be more places where we will need to signal developer that something is wrong. try enforces developer to pay attention to possible errors. Also with throwing errors we can test those invalid cases what we can't do with fatalError.

How does type checking work for factory with different arguments?

Hi, I see that definitions is a mapping from DefinitionKey to DefinitionOf

public func registerFactory<T, F>(tag tag: Tag? = nil, scope: ComponentScope, factory: F) -> DefinitionOf<T, F> {
    let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
    let definition = DefinitionOf<T, F>(factory: factory, scope: scope)
    definitions[key] = definition
    return definition
  }

But factory can be a function that accepts different number of arguments, like

public func register<T, Arg1>(tag tag: Tag? = nil, _ scope: ComponentScope = .Prototype, factory: (Arg1) -> T) -> DefinitionOf<T, (Arg1) -> T> {
    return registerFactory(tag: tag, scope: scope, factory: factory) as DefinitionOf<T, (Arg1) -> T>
  }

When resolve

public func resolve<T, F>(tag tag: Tag? = nil, builder: F->T) throws -> T {
    let key = DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: tag)
    let nilTagKey = tag.map { _ in DefinitionKey(protocolType: T.self, factoryType: F.self, associatedTag: nil) }

    guard let definition = (self.definitions[key] ?? self.definitions[nilTagKey]) as? DefinitionOf<T, F> else {
      throw DipError.DefinitionNotFound(key)
    }

    let usingKey: DefinitionKey? = definition.scope == .ObjectGraph ? key : nil
    return _resolve(tag, key: usingKey, definition: definition, builder: builder)
  }

How does Swift knows the correct factory? How does Swift perform type checking here? I mean you just

public func resolve<T, Arg1>(tag tag: Tag? = nil, withArguments arg1: Arg1) throws -> T {
    return try resolve(tag: tag) { (factory: (Arg1) -> T) in factory(arg1) }
  }

and it works

Default definitions with tags

Currently we can register few definitions with different tags and one with no tag, which will be used as a default if we try to resolve component using unknown tag.

But it does not work the other way - if we don't register definition without tag we will not be able to resolve component without providing tag.

Would it be useful to be able to setup definition with tag as a default and later be able to resolve it without setting tag?

container.register(tag: "facebook", useByDefault: true) { FacebookOAuth() as OAuth }
container.register(tag: "twitter") { TwitterOAuth() as OAuth }
container.register(tag: "google") { GoogleOAuth() as OAuth }

container.resolve() as OAuth // will resolve FacebookOAuth

container.register() { TwitterOAuth() as OAuth } // will override previous default
container.resolve("tumblr") as OAuth // should not resolve, because user explicitly specified unknown tag

Swift 2.3 changes breaks some APIs

Compiling for 2.3 gives errors related to API differences.

For example:

container.resolve(tag: "tag", arguments: arg1, arg2)

in 2.3 must be now changed into:

container.resolve("tag", arguments: arg1, arg2)

This was not happening on previous Dip versions compatible with 2.3

Resolving dependencies and inheritance

I've think I've encountered somewhat unusual bug/design consequence in Dip:

code

class BaseModel: Resolvable {
    var baseService: BaseService!

    func didResolveDependencies() {
    }
}

class ChatModel : BaseModel {    
    var chatService: ChatServiceProtocol!
}

container:

container = DependencyContainer { container in
    let baseService = BaseService()
    container.register(.Singleton) { baseService as BaseService }

    let chatService = ChatService()
    container.register(.Singleton) { chatService as ChatServiceProtocol }

    let baseModel = BaseModel()
    container.register(.Singleton) { baseModel }.resolveDependencies { container, entity in
        entity.baseService = try! container.resolve() as BaseService
    }

    let chatModel = ChatModel()
    container.register(.Singleton) { chatModel }.resolveDependencies { container, entity in
        entity.chatService = try! container.resolve() as ChatServiceProtocol
    }
}

When I initialize ChatModel somewhere, its baseService is not injected, even though it inherits from BaseModel.

let chatModel:ChatModel = ChatModel()
chatModel.chatService // ok
chatModel.baseService // nil

So it would seem that if objects inherit from other objects, then the properties from inherited object aren't resolved properly, because container doesn't trigger it's process in this case. My goal, was, obviously, to have a ChatModel, that would have both baseService and chatService injected, but I wanted to keep baseService in BaseModel. Did I do something wrong? Is it a bug?

I do realize, that I could initialize chatModel like so:

let chatModel = ChatModel()
container.register(.Singleton) { chatModel }.resolveDependencies { container, entity in
    entity.baseService = try! container.resolve() as BaseService
    entity.chatService = try! container.resolve() as ChatServiceProtocol
}

But that's (overall) not the point of having inheritance in the first place.

Resolving weakly typed components

There could be cases when type that should be resolved by container is not defined at compile time. For such situations instead of using generic type container should use type passed as parameter.

This feature is supported by many other containers and it will also open up possibilities for other improvements that (at this moment) are impossible to do with generics (for example registering one component as implementation of several interfaces - type forwarding, or validating container configuration)

Registration api will be still type-safe, so there will be no way to register component for a type that it does not implement.
Requires #71

Rename .Singleton scope to .Container

SwInject calls this equivalent scope Container and it feels more suited, especially as the term Singleton is scary and has a bad reputation especially in the DI world πŸ˜‰

Using .Container as the scope better fits the intent, meaning that the instance will be reused as long as the container exists, and will be purged when the container dies, so the reused instance and container have the same lifespan.

Allow for creating instances in the container right after it is initialized

This is mostly about the Singleton scope. In many cases, the "create and preserve after first resolve was made" approach can cause various issues, especially if some of these singletons would use events/signals for communication. To provide more flexibility, it would be great to provide a way for singletons to be created and preserved right away and not waiting for first resolve reference. This could be configured per singleton or as a general Dip property.

The ability to specify a container class for auto-injection

Thanks for a great implementation of DI framework in pure swift!

It'd be great to have the ability to configure definitions for auto-injections more detailed, e.g. by specifying a container class (BloodMagic has a really nice example of this feature).

The use case may be a TableViewAnimator protocol, which is implemented by a number of concrete classes. The right one should be chosen based on the container class (zUIViewController` subclass).

What is Arg?

Hi, I'm trying Dip. One thing that I wonder is what Arg is, as in

public func resolve<T, Arg1>(tag tag: Tag? = nil, withArguments arg1: Arg1) throws -> T {
    return try resolve(tag: tag) { (factory: (Arg1) -> T) in factory(arg1) }
  }

I can't seem to find where Arg1 is defined

Organise documentation

I think it would be better to make README shorter, demonstrating only basic usage of Dip and list of available features, moving their description to separate md files (or wiki pages?)

Need a way to know when all dependencies are resolved

Most solid IoC containers provide some form of notification after the object that has some dependencies, has the dependencies resolved.

If I set up my dependencies like so:

container = DependencyContainer { container in
  container.register(.Singleton) { LoginService() }
  container.register(.Singleton) { LoginModel() } .resolveDependencies { container, entity in
     entity.loginService = try! container.resolve() as LoginService
  }
}

I need a way to determine within Login Model when loginService is resolved in, because I might need to instantly operate on it for some reason. I cannot do this in initializer, because loginService isn't injected yet. In theory, I could call a method within the resolveDependencies block, but that doesn't feel like an elegant solution. Does Dip provide/could provide something like this?

Dependency resolve timing

Not really an issue, more of a question, so apologies if that's not the right place.

I'm not super familiar with inner Swift workings on a thread/execution level. I've been wondering how does Dip actually works in terms of timing of object creation. If I have an object that has a depedency and this depedency is configured in Dip, if I instantiate this object, are all its depedencies resolved and injected before the next line of code in that thread is executed, or does it happen sometime later?

let myObject = MyObject()
print(myObject.itsDepedency) // is it already injected and ready here?

Obviously, the depedency isn't injected on a initializer level (in MyObject init() method), but when does that happen exactly? Right after?

The reason I'm asking is that in other IoC architectures in other environments, such operation was never safe - you just couldn't assume that all depdencies are injected right away. And thus some callbacks were there just for that (the same thing as with Resolvable and didResolveDependencies I asked for before). However, I'm noticing that in Swift, somehow the dependencies are there right after object instantiation. Is it always like this and is supposed to be like this, or is it just a pure timing coincidence?

Issues with modules' collaboration (Dip 5.0.2)

Hi, @ilyapuchka !
We are using latest Dip version 5.0.2, modules and auto-injection.
We are trying to understand modules' collaboration but nothing works as we expected.

Here the code of DipRoot.swift:

import Dip

//
// 1 ATTEMPT
// Fails with error:
// Failed to auto-inject property "_settingsWireframe" of type SettingsWireframe.
// No definition registered for type: SettingsWireframe, arguments: (), tag: nil.
//
let dipRoot = DependencyContainer() { root in
  Dip.logLevel = .Verbose

  mainModule.collaborate(
    with: root,
    settingsModule
  )

  settingsModule.collaborate(
    with: root,
    mainModule
  )
}

let mainModule = DependencyContainer() { container in
  let mainWireframe = container.register(.singleton) {
    MainWireframe()
      as MainWireframe
  }

  let mainViewController = container.register(.weakSingleton) {
    MainViewController(nibName: "MainViewController", bundle: nil)
      as MainViewController
  }
}

let settingsModule = DependencyContainer() { container in
  let settingsWireframe = container.register(.singleton) {
    SettingsWireframe()
      as SettingsWireframe
  }
}


//
// 2 ATTEMPT
// Works:
//
let mainModule = DependencyContainer() { container in
  Dip.logLevel = .Verbose

  let mainWireframe = container.register(.singleton) {
    MainWireframe()
      as MainWireframe
  }

  let mainViewController = container.register(.weakSingleton) {
    MainViewController(nibName: "MainViewController", bundle: nil)
      as MainViewController
  }

  let settingsWireframe = container.register(.singleton) {
    SettingsWireframe()
      as SettingsWireframe
  }
}

Here the code of Wireframes.swift:

class MainWireframe {
  fileprivate let _settingsWireframe = Injected<SettingsWireframe>()
  var settingsWireframe: SettingsWireframe? { return _settingsWireframe.value }
}

class SettingsWireframe {
}

Here the code of AppDelegate.swift:

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  var window: UIWindow?

  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

    window = UIWindow(frame: UIScreen.main.bounds)
    if let window = window {
      let vc = try! mainModule.resolve() as MainViewController
      let navigation = UINavigationController()
      window.rootViewController = navigation
      navigation.isNavigationBarHidden = true
      navigation.viewControllers = [vc]

      window.backgroundColor = UIColor.white
      window.makeKeyAndVisible()
    }

    return true
  }
}

Full error log here:

Failed to auto-inject property "_mainWireframe" of type MainWireframe.
Failed to auto-inject property "_settingsWireframe" of type SettingsWireframe.
No definition registered for type: SettingsWireframe, arguments: (), tag: nil.
Check the tag, type you try to resolve, number, order and types of runtime
arguments passed to `resolve()` and match them with registered factories
for type SettingsWireframe.
fatal error: 'try!' expression unexpectedly raised an error: Failed to auto-inject
property "_mainWireframe" of type MainWireframe.
Failed to auto-inject property "_settingsWireframe" of type SettingsWireframe.
No definition registered for type: SettingsWireframe, arguments: (), tag: nil.
Check the tag, type you try to resolve, number, order and types of runtime
arguments passed to `resolve()` and match them with registered factories
for type SettingsWireframe.:
file /Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-800.0.46.2/src/swift/stdlib/public/core/ErrorType.swift, line 178

Multi-injection

Sometimes it can be useful to register several implementations for the same protocol and resolve them as an array. Imagine that you have different authorisation services, for instance via Facebook, Twitter and Google, that all implement a common protocol, and you want to present the list of all possible services so that user can choose one of them but your code does not care what concrete service he chose, you just work with it via that common protocol.

With Dip we can do that only with tags, associating different implementations of the same protocol with different tags and resolve them using the same tags. Though it works it introduces coupling between independent parts of the system - point of registration and points of resolution.

protocol OAuthService {
    func doLogin()
}

class FacebookOAuth: OAuthService {
    func doLogin() {
        print("login with facebook")
    }
}

class TwitterOAuth: OAuthService {
    func doLogin() {
        print("login with twitter")
    }
}

class GoogleOAuth: OAuthService {
    func doLogin() {
        print("login with google")
    }
}

let container = DependencyContainer { container in
    container.register(tag: "facebook") { FacebookOAuth() as OAuthService }
    container.register(tag: "twitter") { TwitterOAuth() as OAuthService }
    container.register(tag: "google") { GoogleOAuth() as OAuthService }
}

let services: [OAuthService] = [
    container.resolve(tag: "facebook"),
    container.resolve(tag: "twitter"),
    container.resolve(tag: "google")
]

services[Int(arc4random_uniform(UInt32(3)))].doLogin()

Other IoC containers (like Ninject for .Net) has this feature. The obvious consequence of allowing multiple bindings to the same protocol is that how you resolve them not as an array but as a single instance, what binding (definition in case of Dip) you should choose. Ninject offers different styles of attributes or runtime context checks that are very specific for .Net. If there is no constraints that resolution result is ambiguous and exception is thrown.

Do we need such feature in Dip and if yes than how we should solve the ambiguity problem?

Support watchOS2, tvOS, OSX?

I don't think anything should prevent Dip from working on those other platforms, but I'll have to check first by creating some example before adding that support in the podspec.

Swift 3 issues

Checked on Xcode 8 beta 4

  • Resolving an instance that has weak properties that are not NSObject subclasses causes a crash.
    https://bugs.swift.org/browse/SR-2144
    Workaround: make your weak properties subclasses of NSObject or do not use weak property attribute.
  • Resolving an instance that has IUO properties causes a crash.
    https://bugs.swift.org/browse/SR-2282
    Workaround: use optional properties instead of IUO properties in instances resolved by container.
  • Container is not able to use factories registered with IUO runtime arguments.
    https://bugs.swift.org/browse/SR-2143
    Workaround: do not use IUO as argument type when registering and resolving components
  • Swift compiler may generate warnings when using auto-injection (#139)
    https://bugs.swift.org/browse/SR-2921
  • Swift does not correctly distinguishes between functions accepting single tuple parameter and functions accepting several parameters of tuple elements types, leading to wrong register method calls (see #147 for details). Should be fixed for Swift 4 with SE-0110

Add validate method to container

For tests it could be useful to be able to validate container configuration just with one call, that will check if everything what is registered in the container can be resolved. This method should iterate all definitions and collect all the errors thrown by resolve. Depends on #75 because when iterating definition we can not know their types at compile time.

New Version is broken (resolve fails)

Hi, I've upgraded from Dip 2.0.0 to Dip 3.0.0 (CocoaPods) and my code stopped working, just changing this:
whatever.register(...)
to
whatever.register(tag: ...)

Everything remains the same, so something internal has changed that's preventing from resolving things. The problem comes, after registering some instance, with the appropriate tag, when trying to resolve, seems that it cannot find that instance back.

I insist, I've only updated the pod and added the "tag" label as now is a requirement, everything else remains the same and doesn't work.

Thanks!

Make shared component scope default

In most cases when resolving objects graph we want different consumers of the same service to have a reference to the same instance of that service instead of having unique instances. Then shared components scope makes more sense as a default value.

No injections for circular dependencies with type forwarding

I worked on Dip version for swift 2.3, but after migrating to Swift 3 I have to move to Dip v5.0.2 and my VIPER module assemblies stop working. Circular dependencies doesn't injecting.
This is one example of the module assembly.

import Dip

protocol OnboardModuleFactory {
    func OnboardModule() throws -> (OnboardModuleInput, OnboardViewInput)
}

class OnboardModuleAssembly: OnboardModuleFactory {

    let moduleContainer = DependencyContainer() { container in


        let presenterDefinition = container.register(.shared) { OnboardPresenter() }
            .resolvingProperties { container, presenter in

                presenter.router = try container.resolve()
                presenter.view = try container.resolve()
        }

        let routerDefinition = container.register(.shared) { OnboardRouter()}
        let viewDefinintion = container.register(.shared) { OnboardViewController()}
        .resolvingProperties { container, view in
            view.output = try container.resolve()
        }

        // type forwarding
        container.register(presenterDefinition, type: OnboardViewOutput.self)
        container.register(presenterDefinition, type: OnboardModuleInput.self)
        container.register(viewDefinintion, type: OnboardViewInput.self)
        container.register(viewDefinintion, type: OnboardViewController.self)
        container.register(routerDefinition, type: OnboardRouterInput.self)
    }

    func onboardModule() throws -> (OnboardModuleInput, OnboardViewInput) {
        let view = try moduleContainer.resolve() as OnboardViewController

        // view output is nil
        let presenter = view.output as! OnboardPresenter
        return (presenter, view)
    }
}

Mixing implicit and runtime arguments

Hi!

Thank you so much for Dip - I love using it.

I am however struggling a bit with how to mix argument types, where most arguments should be implicitly registered (by the bootstrapper), but one should be set in runtime.

To clarify, say that I have a view controller with the following initializer:

init(
   service: MyService) {
   self.service = service
   super.init(nibName: type(of: self).className, bundle: nil)
}

I register the vc like this:

dip.register { MyViewController(
   service: $0)
   as MyViewController 
}

When resolving, I just call dip's resolve function and everything's dandy.

However, let's consider that the view controller would also require an object, but a dynamic one that has to be set in runtime. If I use the setup above, I would have to resolve the view controller, then set the object through a public property...but that would force me to make this mandatory object optional or implicitly unwrapped as well as exposing it, which I may not want to do.

Dip allows you to register your types with runtime arguments, but if I try to register the object dependency like this:

dip.register { (obj: MyObject) MyViewController(
   service: $0,
   obj: obj)
   as MyViewController
}

I get the following error:

Anonymous closure arguments cannot be used inside a closure that has explicit arguments.

The examples do not show how you achieve this. Any ides?

Thanks in advance!

Resolvable.didResolveDependencies() called in the wrong order

I have a MyViewController that is StoryboardInstantiatable and Resolvable. It's initialized via:

storyboard.instantiateViewControllerWithIdentifier("myController") as! MyViewController

it has a MyViewModel dependency, resolved as such:

container.register(tag: "MyView") { MyViewController() }.resolveDependencies { container, entity in
  entity.myViewModel = try! container.resolve() as MyViewModel
}

MyViewModel is also registered in container and also has some dependencies.

The problem is that the order of calling didResolveDependencies() is wrong. When the MyViewController is instantiated, its dependencies are resolved, so MyViewModel is resolved. I would expect, that MyViewModel.didResolveDependencies() would be called first, as it is at that time, when this object is created and configured. Instead, MyViewController.didResolveDependencies() is called first, and MyViewModel.didResolveDependencies() is called later, which leads to null pointer errors, because controller assumes that ViewModel is entirely ready to work. But at that point, it's not ready because the starting operations defined in its own didResolveDependencies() weren't executed yet.

[CodeStyle] Normalize header comments + indentation

Header comments

I forgot to normalize the code's head comments on each file. Normally on my OSS projects I try not to use Xcode's generated comment header, but instead mention the MIT License the code is under.

Not sure it's worth quoting the whole MIT licence at the top of the header like I did for OHHTTPStubs and probably something more compact like I did for SwiftGen, or something like this:

//
// Dip
// This code is under MIT Licence. See the LICENCE file for more info.
//

Indentation

Given that now I'm not alone pushing on that project, we should explicitly use the same indentation on all the files of Dip. I recently switched to 2-spaces indentation for Swift instead of the default 4-spaces.

@ilyapuchka Xcode allows you to specify indentation per-file and per-project too, so we can select the project in the workspace and set the "Tab" and "Indent" widths in the inspector, without having to change your global Xcode preferences in case you prefer 4-space width for your other projects and only want to use 2-spaces for Dip.

Swift 3 release process

The current plan as I imagine it is following.

Before Swift 3 is released (probably the end of this week or next week) we can make a prerelease (4.6.1 or maybe better 4.7?) with some API naming improvements ( #105 ). That will let to migrate to new APIs (that are more inline with api guidelines) earlier without source breaking changes and have less headache when migrating to Swift 3.

When Swift 3 is released we will make a new major release 5.0. At this point swift3 branch will be merged to develop and all further development will happen in Swift 3. At the same time we will create new branch swift2.3 that will contain all the same features but will be built with Swift 2.3. Each new release will be provided with binaries built with Swift 3 and Swift 2.3. (not sure how Carthage will handle that, maybe it will load all binaries, maybe none of them). After some reasonable time swift2.3 branch will be deprecated and no longer supported.

Dip 5.0 will aslo contain some breaking changes (like #71 ) and previously deprecated APIs will be removed.

@AliSoftware does it sound good to you?

Update to latest swift version for Linux

There were some changes in swift-corelibs-xctest since I last checked it, so it probably does not work with latest snapshot. Also should include information about latest supported swift snapshot in readme.

Making container final

Should we make DependencyContainer class final? I don't think there are any use cases for subclassing it. If clients want to extend behavior of container they can use composition. Also we never thought about supporting inheritance (maybe some properties should be not internal/private for that) so it can be just safer for clients to make it final.

Objects not identical when using container collaboration

Hello,

First of all, thanks to all for making this well-documented and easy to use (most of the time) framework :) I'm having a hard time understanding why someClass1 and someClass2 in my Playground code below are not pointing to the same object as someClassFromSecondContainer. Because SomeClass/SomeProtocol is registered as a singleton and rootContainer and secondContainer are collaborating I would expect that a resolve via secondContainer would point to the same object as a resolve via rootContainer.

I'm resolving with rootContainer twice in my code below (someClass1 and someClass2) to prove that multiple resolves of a singleton from the the same container do indeed point to the same object, however, a resolve from a collaborating container does not.

Is this a bug in Dip or is this expected behaviour? Thanks so much for your help :)

import Dip

protocol SomeProtocol { }
class SomeClass: SomeProtocol { }

let rootContainer = DependencyContainer() { c in
    c.register(.Singleton) { SomeClass() as SomeProtocol }
}

let secondContainer = DependencyContainer() { c in
    // ... Normally I would register some other things here
}

rootContainer.collaborate(with: secondContainer)
secondContainer.collaborate(with: rootContainer)

let someClass1 = try? rootContainer.resolve() as SomeProtocol
let someClass2 = try? rootContainer.resolve() as SomeProtocol
let someClassFromSecondContainer = try? secondContainer.resolve() as SomeProtocol

if let someClass1 = someClass1 as? SomeClass,
let someClass2 = someClass2 as? SomeClass,
let someClassFromSecondContainer = someClassFromSecondContainer as? SomeClass {

    unsafeAddressOf(someClass1) // "UnsafePointer(0x7FCDB05131B0)"
    unsafeAddressOf(someClass2) // "UnsafePointer(0x7FCDB05131B0)"
    unsafeAddressOf(someClassFromSecondContainer) // "UnsafePointer(0x7FA9B1531710)"

    someClass1 === someClass2 // true
    someClass1 === someClassFromSecondContainer // false
    someClass2 === someClassFromSecondContainer // false
}

Rename a few things.

Given the Swift API design guidelines, I would like to propose renaming a couple of things in Dip.

DependencyContainer(configBlock:) should be DependencyContainer(configuration:) or DependencyContainer(configurationBlock:). The old initializer could be transitioned to a convenience init that delegates to the new one, and marked as deprecated. (Avoid abbreviations.)

resolve(withArguments:) should become either resolve(arguments arguments:) or resolveWithArguments(_:). The old function should be deprecated and delegate to the new one. (When the first argument forms part of a prepositional phrase, give it an argument label.)

Can I auto-inject without registering?

Hi @ilyapuchka

Is it possible to process auto-injections without registering the instance which is not designed to be registered. Like the following:

class Target {
    var prop: Injected<Prop>
    var weakProp: InjectedWeak<Prop>
}


let container = DependencyContainer()
// ...
let target = Target()
container.injectTo(target)

First workaround that come to mind:

let target = Target()
let def = container.register { target }
try! container.resolve() as Target
container.remove(def)

Second (partially copy-pasted from AutoInjection.swift)

let target = Target()
for child in Mirror(reflecting: target).children {
    if let injectedPropertyBox = child.value as? AutoInjectedPropertyBox {
        injectedPropertyBox.resolve(container)
    }
}

BTW, thanks for your work and this awesome library!

Single Dip target causing problems

I've just moved from Xcode 7 / Swift 2.2 to Xcode 8 / Swift 2.3 and Dip is used within app, extension and watch app. In the 2.2 compatible Dip variant the project had separate targets for each platform (iOS, watchOS, tvOS and macOS). The 2.3 variant now has a single Dip target. This seems to be causing issues when you have multiple components for different platforms using Dip within the same project / workspace.

To get my project both build and archive I had to take the 2.2 Dip project file and drop in the 2.3 source files. Without this building my project doesn't appear to build for both iOS and watchOS and then fails since the instance of Dip that is found doesn't support the platform being compiled.

The combined target approach is new to me so maybe there is something that can be done to get it to work but I've not managed to figure it out.

Maybe I can setup a simple project that reproduces this tomorrow, but if you have any comments in the meantime or a solution that would be great ! Note that I'm not using CocoaPods or Carthage but just have Dip pulled with git and added to my project / workspace.

Injected<T> returns nil

I don't know if I'm missing a required setup step when using the Injected pattern, but I just keep getting nil.

I register some dependencies like this:

static func bootstrap() -> DependencyContainer {
  return DependencyContainer { container in
     container.register { MyRepository() as Repository }            
     container.register { MyImporter(repository: $0) as Importer }
   }
}

I then try to resolve the repo in my view controller like this:

var repository = Injected<Repository>()

But when I try to access the value property, I just get nil. I store the container instance in AppDelegate, so it's not garbage collected.

Thanks

Daniel

Make throwing errors more consistent

resolve method can currently throw ResolutionFailed with underlying error or DefinitionNotFound error. ResolutionFailed error wraps any errors thrown by inner resolve calls. Should we make it more consistent and always wrap any error in ResolutionFailed error? Or should we get rid of it and just rethrow what ever inner resolve calls throw without any wrapping?

Storyboards support

With auto-injection merged everything will be ready to support auto-injection in objects created by storyboards. I think it will be nice to release those features together. Actually I already have it implemented in my fork and already tried it in a simple project. @AliSoftware you can check it out in this brunch.

We discussed that we can use separate repository for that feature, like Dip Extensions. The thing is that it is not that much, I think now, that we can put there and maybe it will be not efficient. But on the other side we will need to create separate targets for each platform anyway if we want to keep core of Dip independent from UIKit, whether for CocoaPods or carthage, right?

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.