我正在为项目使用Clean Swift模式。该模式旨在解决MVC模式通常伴随的Massive View Controller问题。
一般想法指出,ViewController将所有处理用户交互的逻辑和一般业务逻辑推迟到一个Interactor。该Interactor会执行其操作,然后将结果数据输出发送到Presenter。 Presenter将该数据包装到“视图模型”中,最后将其传递回ViewController。
为了有效地实现此功能并允许进行模拟,我决定编写类需要采用的协议。
这将使我能够轻松编写许多视图控制器所需的非常通用的功能(例如在加载时显示微调器,在野生动物园视图中打开url等)。
基本上,我的协议是这样定义的:
protocol CleanViewController: class {
associatedType Interactor: CleanInteractor
associatedType Router: CleanRouter
init()
init(interactor: Interactor, router: Router)
var interactor: Interactor! { get set }
var router: Router! { get set }
}
protocol CleanInteractor: class {
associatedType Presenter: CleanPresenter
init()
init(presenter: Presenter)
var presenter: Presenter! { get set }
}
protocol CleanPresenter: class {
associatedType ViewController: CleanViewController
init()
init(viewController: ViewController)
var viewController: ViewController? { get set }
}
protocol CleanRouter: class {
associatedType DataStore: CleanDataStore
associatedType ViewController: CleanViewController
init()
init(dataStore: DataStore, viewController: ViewController)
var dataStore: DataStore! { get set }
var viewController: ViewController? { get set }
}
因此所有这些都是相互交叉引用的。这意味着当我想实现一个新的ViewController时,我需要以某种方式将它们链接在一起。现在,这总是通过始终定义一个setup()
函数来完成,该函数从所有UIViewController的初始化程序中调用。
然后,每当我需要编写一个新的ViewController(例如Login)时,这就是它的实现方式:
protocol LoginDisplayLogic: CleanViewController {
func display(_ some: ViewModel)
}
class LoginViewController<Interactor: LoginBusinessLogic, Router: LoginRoutingLogic>: LoginDisplayLogic, UIViewController {
var interactor: Interactor!
func display(_ some: ViewModel) { // display something }
@IBAction func userDidTapLoginButton(_ sender: Any) { interactor.login() }
}
protocol LoginBusinessLogic: CleanInteractor {
func login()
}
protocol LoginDataStore: CleanDataStore {
var email: String { get set }
var password: String { get set }
}
class LoginInteractor<Presenter: LoginPresentationLogic>: LoginBusinessLogic, LoginDataStore {
var presenter: Presenter!
var email: String
var password: String
func login() {
// do something with email & password
}
}
protocol LoginPresentationLogic: CleanPresenter {
func display(_ loginResult: Result)
}
class LoginPresenter<ViewController: LoginDisplayLogic>: LoginPresentationLogic {
var viewController: ViewController?
func display(_ loginResult: Result) {
// wrap the result to a viewModel and call the viewController
}
}
protocol LoginRoutingLogic: CleanRouter {
func route(to some: Where)
}
class LoginRouter<DataStore: LoginDataStore, ViewController: LoginDisplayLogic>: LoginRoutingLogic {
func route(to some: Where) { // ... }
}
现在,我想使此setup()
函数通用。由于每种协议都定义了自己的关联类型,而所有类型都需要初始化程序,因此我只需要编写一次即可。
所以我要做的是写以下内容:
extension CleanViewController where
Interactor: CleanDataStore,
Interactor.Presenter.ViewController == Self,
Router.ViewController == Self,
Router.DataStore == Interactor {
func setup() {
let presenter = Interactor.Presenter(viewController: self)
let interactor = Interactor(presenter: presenter)
let router = Router(viewController: self, dataStore: interactor)
self.interactor = interactor
self.router = router
}
}
但是如果随后从我的LoginViewController调用此函数,则XCode会引发以下编译错误:
Referencing instance method 'setup()' on 'CleanViewController' requires the types 'LoginViewController<Interactor, Router>' and 'Router.ViewController' be equivalent
Referencing instance method 'setup()' on 'CleanViewController' requires the types 'Interactor' and 'Router.DataStore' be equivalent
Referencing instance method 'setup()' on 'CleanViewController' requires the types 'Router.ViewController' and 'Interactor.Presenter.ViewController' be equivalent
Referencing instance method 'setup()' on 'CleanViewController' requires the types 'Router' and 'Interactor.Presenter.ViewController.Router.ViewController.Router' be equivalent
在我看来这很奇怪,因为在声明setup()
的扩展名中应确保所有这些关联的类型都是等效的。
我知道这篇文章是一篇冗长而复杂的文章,但是我在此工作中到底缺少什么呢?