Taking into account the VIPER structure
I have two modules, A and B. The first module A, via presenter, wants to perform an action that has to be done in the module B, so tells its wireframe to do it. The question is, who is responsible of instantiate the whole module (view, interactor, presenter...). I saw some examples with different approaches:
- Create all the modules in the beginning of the app.
- Create the whole module in the wireframe of the module, so in this case a class method of BWireframe instantites all the B module.
Taking into account that the wireframe is responsible for routing, is it also responsible of creating its module?
TL;DR: I would recommend you should use a DI framework like Typhoon and let that handle instantiation.
The reason you probably won't want your wireframes instantiating everything in a VIPER modules (View, Presenter, Interactor, DataManager) is that you'll be creating dependencies directly to each of those components.
Dependencies make software resistant to change: if we think of it with our onion architecture / hexagonal architecture hats on, the wireframe would cross the boundaries of at least two separate layers of your onion by knowing about not only the view, but the data manager.
This forces us to treat wireframes as a generic infrastructure class: i.e., something on the outer-most layer of your application.
However, this contradicts with the wireframe's other (and the more real) responsibility: doing navigation. While this is still the infrastructure layer, it belongs firmly and specifically in UIKit
(-presentViewController:
etc.).
So IMHO, VIPER's wireframe is doing too much.
The only sensible thing is to split the two responsibilities:
- initialization of VIPER classes: you can introduce a factory, or use DI tools which are designed to solve this problem
- doing navigation: this stays within the remit of a
Wireframe
.
Additional notes
If you're wondering "who instantiates the next module?", then again, IMHO, I think it's not right that some Module A
's wireframe talks to Module B
's wireframe despite what the VIPER post says.
The reason is that Module A
would now be made to depend on Module B
, causing a tight coupling: this defeats the purpose of modules. Much more discussion about this topic over at VIPER-TODO project at GitHub =]
Hope this helps.
I think that the entering gate to a module is the wireframe so I agree with your second approach.
Creating a module includes the creation of all of its objects and it seems to me kind of wasteful.
Use this Xcode plugin (https://github.com/natangr/ViperTemplate) to create and initiate VIPER files automatically.
Read this post for more advanced tips on VIPER instantiation (and explanation of above plugin): https://www.ckl.io/blog/best-practices-viper-architecture/
Having the module initialization code on its own Router will eliminate a bunch of code repetition, specially for huge projects.
You need to create these extensions once:
// ReusableView.swift
protocol ReusableView: class {}
extension ReusableView {
static var reuseIdentifier: String {
return String(describing: self)
}
}
// UIViewController.swift
extension UIViewController: ReusableView { }
// UIStoryboard.swift
extension UIStoryboard {
func instantiateViewController() -> T where T: ReusableView {
return instantiateViewController(withIdentifier: T.reuseIdentifier) as! T
}
}
And then, leave initialization code on the router of each VIPER module:
// MainSearchRouter.swift
class MainSearchRouter {
// MARK: Properties
weak var view: UIViewController?
// MARK: Static methods
static func setupModule() -> MainSearchViewController {
let viewController = UIStoryboard(name: MainSearchViewController.storyboardName, bundle: nil).instantiateViewController() as MainSearchViewController
let presenter = MainSearchPresenter()
let router = MainSearchRouter()
let interactor = MainSearchInteractor()
viewController.presenter = presenter
presenter.view = viewController
presenter.router = router
presenter.interactor = interactor
router.view = viewController
interactor.output = presenter
return viewController
}
}
It might seem like a lot of steps, but good news: the aforementioned plugin automates that as well!