Swinject: Register object if it conforms to multiple interfaces

Created on 16 Apr 2017  Â·  7Comments  Â·  Source: Swinject/Swinject

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() {
    }
}
question

Most helpful comment

@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)
        }
    }
}

All 7 comments

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().
screen shot 2017-04-22 at 10 48 11 am
No idea why it did happen. I created a sample project in zip archive.
ios-swift-riblet-github-client.zip
Steps to reproduce:

  1. Set break point at func didTriggerViewDidLoad() in file LoginPresenter.swift
  2. Set break point at func success() in file LoginPresenter.swift
  3. Run application, func didTriggerViewDidLoad() will be called first time as expected (LoginViewController calls it).
  4. Click Sign In button, then func didTriggerViewDidLoad() will be called instead of func success() ¯\_(ツ)_/¯

How it's expected to work:

  1. 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.

Was this page helpful?
0 / 5 - 0 ratings