Swift 如何创建像 UIViewController 这样的通用 MVP
Swift how to create a generic MVP like UIViewController
我想删除重复的代码,所以我想创建一个简单的 MVP 基础视图控制器,它将模型、视图和演示者类型结合在一起并自动连接它们,例如:
class BaseMvpViewController<M: MvpModel, V: MvpView, P: MvpPresenter>: UIViewController {
我的模型和视图是空协议:
protocol MvpModel {}
protocol MvpView: class {} // class is needed for weak property
主持人看起来像这样:
protocol MvpPresenter {
associatedtype View: MvpView
weak var view: View? { get set }
func onAttach(view: View)
func onDetach(view: View)
}
这是我的全部BaseMvpViewController
:
class BaseMvpViewController<M: MvpModel, V, P: MvpPresenter>: UIViewController, MvpView {
typealias View = V
var model: M? = nil
var presenter: P!
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
deinit {
presenter.onDetach(view: self as! View)
}
override func viewDidLoad() {
createPresenter()
super.viewDidLoad()
presenter.onAttach(view: self as! View)
}
func createPresenter() {
guard presenter != nil else {
preconditionFailure("Presenter was not created or it was not assigned into the `presenter` property!")
}
}
}
问题是 V
必须没有协议,即不能 V: MvpView
。否则 VC 的特定实现必须有 class/struct 而不仅仅是 MvpView
的协议。我所有的观点都只是协议,我的 VC 将实施它们,例如
class MyViewController: BaseMvpViewController<MyModel, MyView, MyPresenter>, MyView
现在编译器在 onAttach()
和 onDetach()
方法中抱怨 "argument type 'V' does not conform to expected type 'MvpView'"
所以我尝试了一个扩展:
extension BaseMvpViewController where V: MvpView {
override func viewDidLoad() {
presenter.onAttach(view: self as! View)
}
}
又一个编译器错误:"cannot invoke 'onAttach' with an argument list of type '(view: V)'"。还有一个小的编译错误"Members of constrained extensions cannot be declared @objc" where I override func viewDidLoad()
in the extension.这可以通过我自己的方法修复,并在自定义 class 中从 viewDidLoad
调用那个方法。知道如何实现我想要的吗?
这是一个类似于 的 similar/same 问题,但从那时起,Swift 世界可能已经有所改进。还是我真的遇到了当前 Swift 能力的硬性限制?
终于找到解决办法了,问题出在铸造self as! View
,一定是self as! P.View
。并且不能有用于查看的基本协议,因为协议在 Swift 中不符合自身。这是我的完整代码:
protocol MvpPresenter {
associatedtype View
var view: View? { get set }
var isAttached: Bool { get }
func onAttach(view: View)
func onDetach(view: View)
}
/// Default implementation for the `isAttached()` method just checks if the `view` is non nil.
extension MvpPresenter {
var isAttached: Bool { return view != nil }
}
class BaseMvpViewController<M, V, P: MvpPresenter>: UIViewController {
typealias View = V
var viewModel: M? = nil
private(set) var presenter: P!
//MARK: - Initializers
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override public init(nibName: String?, bundle: Bundle?) {
super.init(nibName: nibName, bundle: bundle)
}
deinit {
presenter.onDetach(view: self as! P.View)
}
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
presenter = createPresenter()
}
override func viewWillAppear(_ animated: Bool) {
guard let view = self as? P.View else {
preconditionFailure("MVP ViewController must implement the view protocol `\(View.self)`!")
}
super.viewWillAppear(animated)
if (!presenter.isAttached) {
presenter.onAttach(view: view)
}
}
//MARK: - MVP
/// Override and return a presenter in a subclass.
func createPresenter() -> P {
preconditionFailure("MVP method `createPresenter()` must be override in a subclass and do not call `super.createPresenter()`!")
}
}
和一个示例 VC:
class MyGenericViewController: BaseMvpViewController<MyModel, MyView, MyPresenter>, MyView {
...
override func createPresenter() -> MainPresenter {
return MyPresenter()
}
...
}
这个 VC 将自动有一个 viewModel
属性 类型 MyModel
(可以是任何东西,例如结构、class、枚举等), 属性 presenter
类型 MyPresenter
,此演示者将自动附加到 viewDidLoad
和 viewWillAppear
之间。必须重写一个方法,createPresenter()
您必须在其中创建和 return 一个演示者。这是在自定义 VC 的 viewDidLoad
方法之前调用的。 Presenter 在 deinit
.
中分离
最后一个问题是通用视图控制器不能在界面生成器 (IB) 中使用,因为 IB 通过 Objective-C 运行时与代码对话并且它不知道真正的泛型,因此看不到我们的通用 VC。从 storyboard/xib 实例化通用 VC 时应用程序崩溃。虽然有一个技巧可以解决这个问题。在 storyboard/xib 的任何实例化之前,只需将通用 VC 手动加载到 Objective-C 运行时。好的是 AppDelegate
的 init
方法:
init() {
...
MyGenericViewController.load()
...
}
编辑 1:
我在这个 SO answer
中找到了将通用 VC 加载到 Objective-C 运行时
编辑 2:
示例演示者 class。强制性的东西是 typealias
、weak var view: View?
和 onAttach
& onDetach
方法。还提供了 attach/detach 方法的最小实现。
class SamplePresenter: MvpPresenter {
// These two are needed!
typealias View = SampleView
weak var view: View?
private let object: SomeObject
private let dao: SomeDao
//MARK: - Initializers
/// Sample init method which accepts some parameters.
init(someObject id: String, someDao dao: SomeDao) {
guard let object = dao.getObject(id: id) else {
preconditionFailure("Object does not exist!")
}
self.object = object
self.dao = dao
}
//MARK: - MVP. Both the onAttach and onDetach must assign the self.view property!
func onAttach(view: View) {
self.view = view
}
func onDetach(view: View) {
self.view = nil
}
//MARK: - Public interface
/// Sample public method that can be called from the view (e.g. a ViewController)
/// that will load some data and tell the view to display them.
func loadData() {
guard let view = view else {
return
}
let items = dao.getItem(forObject: object)
view.showItems(items)
}
//MARK: - Private
}
我想删除重复的代码,所以我想创建一个简单的 MVP 基础视图控制器,它将模型、视图和演示者类型结合在一起并自动连接它们,例如:
class BaseMvpViewController<M: MvpModel, V: MvpView, P: MvpPresenter>: UIViewController {
我的模型和视图是空协议:
protocol MvpModel {}
protocol MvpView: class {} // class is needed for weak property
主持人看起来像这样:
protocol MvpPresenter {
associatedtype View: MvpView
weak var view: View? { get set }
func onAttach(view: View)
func onDetach(view: View)
}
这是我的全部BaseMvpViewController
:
class BaseMvpViewController<M: MvpModel, V, P: MvpPresenter>: UIViewController, MvpView {
typealias View = V
var model: M? = nil
var presenter: P!
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
deinit {
presenter.onDetach(view: self as! View)
}
override func viewDidLoad() {
createPresenter()
super.viewDidLoad()
presenter.onAttach(view: self as! View)
}
func createPresenter() {
guard presenter != nil else {
preconditionFailure("Presenter was not created or it was not assigned into the `presenter` property!")
}
}
}
问题是 V
必须没有协议,即不能 V: MvpView
。否则 VC 的特定实现必须有 class/struct 而不仅仅是 MvpView
的协议。我所有的观点都只是协议,我的 VC 将实施它们,例如
class MyViewController: BaseMvpViewController<MyModel, MyView, MyPresenter>, MyView
现在编译器在 onAttach()
和 onDetach()
方法中抱怨 "argument type 'V' does not conform to expected type 'MvpView'"
所以我尝试了一个扩展:
extension BaseMvpViewController where V: MvpView {
override func viewDidLoad() {
presenter.onAttach(view: self as! View)
}
}
又一个编译器错误:"cannot invoke 'onAttach' with an argument list of type '(view: V)'"。还有一个小的编译错误"Members of constrained extensions cannot be declared @objc" where I override func viewDidLoad()
in the extension.这可以通过我自己的方法修复,并在自定义 class 中从 viewDidLoad
调用那个方法。知道如何实现我想要的吗?
这是一个类似于
终于找到解决办法了,问题出在铸造self as! View
,一定是self as! P.View
。并且不能有用于查看的基本协议,因为协议在 Swift 中不符合自身。这是我的完整代码:
protocol MvpPresenter {
associatedtype View
var view: View? { get set }
var isAttached: Bool { get }
func onAttach(view: View)
func onDetach(view: View)
}
/// Default implementation for the `isAttached()` method just checks if the `view` is non nil.
extension MvpPresenter {
var isAttached: Bool { return view != nil }
}
class BaseMvpViewController<M, V, P: MvpPresenter>: UIViewController {
typealias View = V
var viewModel: M? = nil
private(set) var presenter: P!
//MARK: - Initializers
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override public init(nibName: String?, bundle: Bundle?) {
super.init(nibName: nibName, bundle: bundle)
}
deinit {
presenter.onDetach(view: self as! P.View)
}
//MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
presenter = createPresenter()
}
override func viewWillAppear(_ animated: Bool) {
guard let view = self as? P.View else {
preconditionFailure("MVP ViewController must implement the view protocol `\(View.self)`!")
}
super.viewWillAppear(animated)
if (!presenter.isAttached) {
presenter.onAttach(view: view)
}
}
//MARK: - MVP
/// Override and return a presenter in a subclass.
func createPresenter() -> P {
preconditionFailure("MVP method `createPresenter()` must be override in a subclass and do not call `super.createPresenter()`!")
}
}
和一个示例 VC:
class MyGenericViewController: BaseMvpViewController<MyModel, MyView, MyPresenter>, MyView {
...
override func createPresenter() -> MainPresenter {
return MyPresenter()
}
...
}
这个 VC 将自动有一个 viewModel
属性 类型 MyModel
(可以是任何东西,例如结构、class、枚举等), 属性 presenter
类型 MyPresenter
,此演示者将自动附加到 viewDidLoad
和 viewWillAppear
之间。必须重写一个方法,createPresenter()
您必须在其中创建和 return 一个演示者。这是在自定义 VC 的 viewDidLoad
方法之前调用的。 Presenter 在 deinit
.
最后一个问题是通用视图控制器不能在界面生成器 (IB) 中使用,因为 IB 通过 Objective-C 运行时与代码对话并且它不知道真正的泛型,因此看不到我们的通用 VC。从 storyboard/xib 实例化通用 VC 时应用程序崩溃。虽然有一个技巧可以解决这个问题。在 storyboard/xib 的任何实例化之前,只需将通用 VC 手动加载到 Objective-C 运行时。好的是 AppDelegate
的 init
方法:
init() {
...
MyGenericViewController.load()
...
}
编辑 1: 我在这个 SO answer
中找到了将通用 VC 加载到 Objective-C 运行时编辑 2:
示例演示者 class。强制性的东西是 typealias
、weak var view: View?
和 onAttach
& onDetach
方法。还提供了 attach/detach 方法的最小实现。
class SamplePresenter: MvpPresenter {
// These two are needed!
typealias View = SampleView
weak var view: View?
private let object: SomeObject
private let dao: SomeDao
//MARK: - Initializers
/// Sample init method which accepts some parameters.
init(someObject id: String, someDao dao: SomeDao) {
guard let object = dao.getObject(id: id) else {
preconditionFailure("Object does not exist!")
}
self.object = object
self.dao = dao
}
//MARK: - MVP. Both the onAttach and onDetach must assign the self.view property!
func onAttach(view: View) {
self.view = view
}
func onDetach(view: View) {
self.view = nil
}
//MARK: - Public interface
/// Sample public method that can be called from the view (e.g. a ViewController)
/// that will load some data and tell the view to display them.
func loadData() {
guard let view = view else {
return
}
let items = dao.getItem(forObject: object)
view.showItems(items)
}
//MARK: - Private
}