[英]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的特定实现必须具有类/结构,而不仅仅是MvpView
的协议。 我所有的观点只是协议,我的VC将实现它们,例如
class MyViewController: BaseMvpViewController<MyModel, MyView, MyPresenter>, MyView
现在,编译器在onAttach()
和onDetach()
方法中抱怨“参数类型'V'不符合预期的类型'MvpView'”
所以我尝试了一个扩展:
extension BaseMvpViewController where V: MvpView {
override func viewDidLoad() {
presenter.onAttach(view: self as! View)
}
}
另一个编译器错误:“无法使用类型'(view:V)'的参数列表调用'onAttach'”。 还有另一个小的编译错误“无法将受约束扩展的成员声明为@objc”,其中我在扩展中override func viewDidLoad()
。 可以通过我自己的方法并在自定义类的viewDidLoad
中调用该方法来解决此问题。 知道如何实现我想要的吗?
这是一个类似/相同的问题,例如不支持将某些协议用作遵循另一协议的具体类型,但此后在Swift世界中可能有所改进。 还是我真的在当前Swift的功能上遇到了硬性限制?
在终于找到了解决方案时,问题出在将self as! View
self as! View
,一定是self as! P.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
类型的viewModel
属性(可以是MyModel
struct,class,enum等的任何内容), MyPresenter
类型的属性presenter
,并且此演示者将自动附加在viewDidLoad
和viewWillAppear
之间。 必须重写一种方法, createPresenter()
必须在其中创建并返回演示者。 在自定义VC的viewDidLoad
方法之前调用此方法。 演示者在deinit
分离。
最后一个问题是,通用视图控制器不能在接口构建器(IB)中使用,因为IB通过Objective-C运行时与代码进行对话,并且不知道真正的通用,因此看不到我们的通用VC。 从情节提要/ xib实例化通用VC时,应用程序崩溃。 有一个技巧可以解决此问题。 从情节提要/ xib进行任何实例化之前,只需将通用VC手动加载到Objective-C运行时即可。 好在AppDelegate
的init
方法中:
init() {
...
MyGenericViewController.load()
...
}
编辑1:我在此SO答案https://stackoverflow.com/a/43896830/671580中发现了将通用VC加载到Objective-C运行时的过程
编辑2:示例演示者类。 必不可少的是typealias
, weak var view: View?
以及onAttach
和onDetach
方法。 还提供了附加/分离方法的最小实现。
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
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.