AliSoftware / Dip

Simple Swift Dependency container. Use protocols to resolve your dependencies and avoid singletons / sharedInstances!
MIT License
978 stars 75 forks source link

Resolving dependencies for UIViewControllers at runtime without using DipUI #187

Closed onlyamessenger closed 6 years ago

onlyamessenger commented 6 years ago

When using StoryBoards to instantiate UIViewControllers, resolving dependencies is very straight forward. Currently, I do it as follows:

container.register(storyboardType: MessagesController.self, tag: nil)
            .resolvingProperties { container, controller in
                controller.presenter = MessagesPresenter(
                    view: controller,
                    mapper: ViewMessageMapper(),
                    messageRepository: try container.resolve() as MessageRepository,
                    userRepository: try container.resolve() as UserRepository
                )
            }

How would I accomplish this for a UIViewController that is created using init() and not instantiated by a StoryBoard? I really need this since some of the UIViewControllers in my project are created completely in code and have no StoryBoard files. I thought of getting a reference to container in the controller, but that would just be creating the ServiceLocator anti-pattern.

This is how these controllers are typically created in response to user actions:

let chat = ChatController()
chat.chat = chats[indexPath.row]
navigationController?.pushViewController(chat, animated: true)
ilyapuchka commented 6 years ago

If you are creating controller in code you can pass all dependencies in init and use auto-wiring (will work the same way even if there are not dependencies to pass in). If you still want to use init() you'll have to make your properties for dependencies mutable to be able to inject them manually in resolvingProperties, or use auto-injection.

ilyapuchka commented 6 years ago

The only thing that DipUI does is that it registers "dummy" factory which will never be called (unless controller is resolved manually and not created from storyboard) and calls resolve as soon as controller is instantiated from storyboard. When you create controllers in code it's no different from resolving any other kind of types.

onlyamessenger commented 6 years ago

Sorry, I still don't understand how that would work, since ChatController is not instantiated in the composition root. Could you perhaps provide a short example?

ilyapuchka commented 6 years ago

You don't have to instantiate view controllers in composition root. You can create a factory that will create this controller when you ask it. Container can play as such factory if you use it through some factory interface, not with resolve:

protocol ChatViewControllerProvider {
  func makeChatViewController() -> ChatViewController
}

extension Container: ChatViewControllerProvider {
  func makeChatViewController() -> ChatViewController {
    return try! resolve() as ChatViewController
  }
}

// or

class ChatViewControllerFactory: ChatViewControllerProvider {
  func makeChatViewController() -> ChatViewController {
    return ChatViewController()
  }
}

container.register { ChatViewControllerFactory() as ChatViewControllerProvider }
onlyamessenger commented 6 years ago

Okay, I think using what you described here would be best:

if you still want to use init() you'll have to make your properties mutable to be able to inject them manually in resolvingProperties

The only question I have then is how should ChatController be registered to enable resolvingProperties to be called?

ilyapuchka commented 6 years ago

The same way as any other type.

container.register(ChatViewController.init).resolvingProperties { ... }
onlyamessenger commented 6 years ago

Thank you, I didn't see that registration use case in the docs. Are you accepting PR's for documentation? I think it would be useful to have some Clean Architecture use cases shown with DIP.

onlyamessenger commented 6 years ago

Getting the following compiler error:

Ambiguous reference to member 'init(nibName:bundle:)'

ilyapuchka commented 6 years ago

I think wiki contains excessive docs and enough examples, please check it. https://github.com/AliSoftware/Dip/wiki/Tips-&-Tricks#registering-using-initializersfactory-methods-v-50

onlyamessenger commented 6 years ago

Thanks, managed to convert ChatController so it's instantiated by StoryBoard. Couldn't get it to resolve properly otherwise.