Hi guys,
I'm investigating into the issue how to resolve dependencies if resolving object conforms to multiple interfaces. For example I have an object of class LoginPresenter.
class LoginPresenter: LoginViewOutput, LoginInteractorOutput {
var view: LoginViewInput?
var interactor: LoginInteractorInput?
func didTriggerViewDidLoad() {
self.interactor?.executeRequest()
}
}
It does conform to protocols LoginViewOutput and LoginInteractorOutput.
protocol LoginInteractorOutput {}
protocol LoginViewOutput {
func didTriggerViewDidLoad()
}
So in dependencies graph it can be resolved via both declarations below:
let presenter = resolver.resolve(LoginViewOutput.self)
or
let presenter = resolver.resolve(LoginInteractorOutput.self)
I like to use VIPER architecture in my projects. So when I create view object, then I resolve output(presenter) via LoginViewOutput.self type
container.register(LoginViewInput.self) { _ in
LoginViewController(nibName: "LoginViewController", bundle: nil)
}.initCompleted { (resolver, controller) in
let controller = controller as! LoginViewController
controller.output = resolver.resolve(LoginViewOutput.self)
}
When I create interactor object, then I resolve output(presenter) via LoginInteractorOutput.self type
container.register(LoginInteractorInput.self) { _ in
LoginInteractor()
}.initCompleted { (resolver, interactor) in
let interactor = interactor as! LoginInteractor
interactor.output = resolver.resolve(LoginInteractorOutput.self)
}
So how I can register presenter object to make ability resolve it via LoginViewOutput and LoginInteractorOutput?
For details see assembly class implementation:
class LoginAssembly: Assembly {
func assemble(container: Container) {
container.register(LoginViewInput.self) { _ in
LoginViewController(nibName: "LoginViewController", bundle: nil)
}.initCompleted { (resolver, controller) in
let controller = controller as! LoginViewController
controller.output = resolver.resolve(LoginViewOutput.self)
}
container.register(LoginViewOutput.self) { _ in
LoginPresenter()
}.initCompleted { (resolver, presenter) in
let presenter = presenter as! LoginPresenter
presenter.view = resolver.resolve(LoginViewInput.self)
presenter.interactor = resolver.resolve(LoginInteractorInput.self)
}
container.register(LoginInteractorInput.self) { _ in
LoginInteractor()
}.initCompleted { (resolver, interactor) in
let interactor = interactor as! LoginInteractor
interactor.output = resolver.resolve(LoginInteractorOutput.self)
}
}
}
And all related classes together:
protocol LoginViewInput {}
class LoginViewController: UIViewController, LoginViewInput {
var output: LoginViewOutput?
override func viewDidLoad() {
super.viewDidLoad()
self.output?.didTriggerViewDidLoad()
}
}
protocol LoginInteractorOutput {}
protocol LoginViewOutput {
func didTriggerViewDidLoad()
}
class LoginPresenter: LoginViewOutput, LoginInteractorOutput {
var view: LoginViewInput?
var interactor: LoginInteractorInput?
func didTriggerViewDidLoad() {
self.interactor?.executeRequest()
}
}
protocol LoginInteractorInput {
func executeRequest()
}
class LoginInteractor: LoginInteractorInput {
var output: LoginInteractorOutput?
func executeRequest() {
}
}
Thanks @igorkotkovets for the question. I will answer later.
@igorkotkovets have you tried the method suggested in #227?
@jakubvano thank you, I'll check
@jakubvano I checked and method suggested in #227 does work
class LoginAssembly: Assembly {
typealias LoginPresenterType = LoginViewOutput & LoginInteractorOutput
func assemble(container: Container) {
container.register(LoginViewInput.self) { _ in
LoginViewController(nibName: "LoginViewController", bundle: nil)
}.initCompleted { (resolver, controller) in
let controller = controller as? LoginViewController
controller?.output = resolver.resolve(LoginViewOutput.self)
}
container.register(LoginViewOutput.self) { resolver in
resolver.resolve(LoginPresenterType.self)!
}
container.register(LoginInteractorOutput.self) { resolver in
resolver.resolve(LoginPresenterType.self)!
}
container.register(LoginInteractorInput.self) { _ in
LoginInteractor()
}.initCompleted { (resolver, interactor) in
let interactor = interactor as? LoginInteractor
interactor?.output = resolver.resolve(LoginInteractorOutput.self)
}
container.register(LoginPresenterType.self) { _ in
LoginPresenter()
}.initCompleted { (resolver, presenter) in
let presenter = presenter as? LoginPresenter
presenter?.view = resolver.resolve(LoginViewInput.self)
presenter?.interactor = resolver.resolve(LoginInteractorInput.self)
}
}
}
I've checked a case with defining typealias in another project a got a strange behavior
In my case I defined typealias LoginPresenterTypeAlias = LoginViewOutput & LoginInteractorOutput
And the method func didTriggerViewDidLoad() was called instead of calling func success().

No idea why it did happen. I created a sample project in zip archive.
ios-swift-riblet-github-client.zip
Steps to reproduce:
func didTriggerViewDidLoad() in file LoginPresenter.swiftfunc success() in file LoginPresenter.swift func didTriggerViewDidLoad() will be called first time as expected (LoginViewController calls it). func didTriggerViewDidLoad() will be called instead of func success() ¯\_(ツ)_/¯How it's expected to work:
LoginPresenter calls interactor?.login(with: login, password), then LoginInteractor calls output?.success() Environment
Apple Swift version 3.1 (swiftlang-802.0.53 clang-802.0.42)
Target: x86_64-apple-macosx10.9
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin16.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
===========================
So I defined the following Assembly:
class LoginRibletAssembly: Assembly {
typealias LoginPresenterTypeAlias = LoginViewOutput & LoginInteractorOutput
func assemble(container: Container) {
container.register(LoginViewInput.self) { _ in
LoginViewController(nibName: "LoginViewController", bundle: nil)
}
.initCompleted { resolver, view in
let view = view as? LoginViewController
view?.output = resolver.resolve(LoginViewOutput.self)
}
container.register(LoginViewOutput.self) { resolver in
return resolver.resolve(LoginPresenterTypeAlias.self)!
}
container.register(LoginInteractorOutput.self) { resolver in
return resolver.resolve(LoginPresenterTypeAlias.self)!
}
container.register(LoginPresenterTypeAlias.self) { _ in
LoginPresenter()
}
.initCompleted { resolver, presenter in
let presenter = presenter as? LoginPresenter
presenter?.view = resolver.resolve(LoginViewInput.self)
presenter?.interactor = resolver.resolve(LoginInteractorInput.self)
presenter?.router = resolver.resolve(LoginRouterInput.self)
}
container.register(LoginInteractorInput.self) { _ in
LoginInteractor()
}
.initCompleted { resolver, interactor in
let interactor = interactor as? LoginInteractor
interactor?.output = resolver.resolve(LoginInteractorOutput.self)
}
container.register(LoginRouterInput.self) { _ in
LoginRouter()
}
.initCompleted { resolver, router in
let router = router as? LoginRouter
// router?.resolver = resolver
router?.view = resolver.resolve(LoginViewInput.self)
}
}
}
My objects does look following:
protocol LoginViewInput: class {
func getViewController() -> UIViewController?
func display(error: String)
}
protocol LoginViewOutput: class {
func didTriggerViewDidLoad()
func didTriggerLogin(with user: String?, _ password: String?)
}
protocol LoginInteractorInput: class {
func isValid(login: String?) -> Bool
func isValid(password: String?) -> Bool
func login(with user: String?, _ password: String?)
}
protocol LoginInteractorOutput: class {
func success()
}
protocol LoginRouterInput: class {
func presentMainScreen()
}
LoginPresenter.swift
class LoginPresenter {
weak var view: LoginViewInput?
var interactor: LoginInteractorInput?
var router: LoginRouterInput?
deinit {
}
}
extension LoginPresenter: LoginViewOutput {
func didTriggerViewDidLoad() {
print("didTriggerViewDidLoad")
}
func didTriggerLogin(with user: String?, _ password: String?) {
guard interactor?.isValid(login: user) ?? false, interactor?.isValid(password: password) ?? false else {
view?.display(error: "Invalid user")
return
}
if let login = user, let password = password {
interactor?.login(with: login, password)
}
}
}
extension LoginPresenter: LoginInteractorOutput {
func success() {
print("success")
router?.presentMainScreen()
}
}
LoginInteractor.swift
class LoginInteractor {
weak var output: LoginInteractorOutput?
}
extension LoginInteractor: LoginInteractorInput {
func login(with user: String?, _ password: String?) {
print("func login(with user: String, _ password: String)")
output?.success()
}
func isValid(login: String?) -> Bool {
return true
}
func isValid(password: String?) -> Bool {
return true
}
}
You are welcome for any questions about demo project and thanks for helping.
PS: A workaround don't use typealias and use LoginPresenter type
Looks like it's a bug of Swift compiler. I checked that incorrect function is called.
I have not been able to reproduce the issue using Xcode 9.2 (Swift 3.2). It is possible - as you mentioned - that it was a bug in the compiler which has been fixed.
Btw, now that type forwarding is implemented, this LoginPresenter registration can be simplified to:
container.register(LoginViewOutput) { _ in LoginPresenter() }
.implements(LoginInteractorOutput.self)
I will close this for now, as it is quite an old issue and appears to have already been fixed. Feel free to reopen this, if there are still some related problems.
Most helpful comment
@jakubvano I checked and method suggested in #227 does work