繁体   English   中英

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的特定实现必须具有类/结构,而不仅仅是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 ,并且此演示者将自动附加在viewDidLoadviewWillAppear之间。 必须重写一种方法, createPresenter()必须在其中创建并返回演示者。 在自定义VC的viewDidLoad方法之前调用此方法。 演示者在deinit分离。

最后一个问题是,通用视图控制器不能在接口构建器(IB)中使用,因为IB通过Objective-C运行时与代码进行对话,并且不知道真正的通用,因此看不到我们的通用VC。 从情节提要/ xib实例化通用VC时,应用程序崩溃。 有一个技巧可以解决此问题。 从情节提要/ xib进行任何实例化之前,只需将通用VC手动加载到Objective-C运行时即可。 好在AppDelegateinit方法中:

init() {
    ...
    MyGenericViewController.load()
    ...
}

编辑1:我在此SO答案https://stackoverflow.com/a/43896830/671580中发现了将通用VC加载到Objective-C运行时的过程

编辑2:示例演示者类。 必不可少的是typealiasweak var view: View? 以及onAttachonDetach方法。 还提供了附加/分离方法的最小实现。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM