Swift 如何创建像 UIViewController 这样的通用 MVP

she*_*lll 2 generics mvp ios swift

我想删除重复的代码,所以我想创建一个简单的 MVP 基础视图控制器,它将模型、视图和演示者类型联系在一起并自动连接它们,例如:

class BaseMvpViewController<M: MvpModel, V: MvpView, P: MvpPresenter>: UIViewController {
Run Code Online (Sandbox Code Playgroud)

我的模型和视图是空协议的地方:

protocol MvpModel {}
protocol MvpView: class {} // class is needed for weak property
Run Code Online (Sandbox Code Playgroud)

主持人看起来像这样:

protocol MvpPresenter {
    associatedtype View: MvpView
    weak var view: View? { get set }
    func onAttach(view: View)
    func onDetach(view: View)
}
Run Code Online (Sandbox Code Playgroud)

这是我的全部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!")
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

问题是V必须没有协议即不能是V: MvpView。否则,VC 的特定实现必须有一个类/结构,而不仅仅是MvpView. 我所有的观点都只是协议,我的 VC 将实施它们,例如

class MyViewController: BaseMvpViewController<MyModel, MyView, MyPresenter>, MyView
Run Code Online (Sandbox Code Playgroud)

现在编译器在onAttach()onDetach()方法中抱怨“参数类型'V'不符合预期类型'MvpView'”

所以我尝试了一个扩展:

extension BaseMvpViewController where V: MvpView {
    override func viewDidLoad() {
        presenter.onAttach(view: self as! View)
    }
}
Run Code Online (Sandbox Code Playgroud)

另一个编译器错误:“无法使用类型为 '(view: V)' 的参数列表调用 'onAttach'”。还有另一个小的编译错误“受限扩展的成员不能声明@objc”,我override func viewDidLoad()在扩展中的位置。这可以通过我自己的方法并从viewDidLoad自定义类中调用该方法来解决。知道如何实现我想要的吗?

这是一个类似/相同的问题,例如不支持使用某些协议作为符合另一个协议的具体类型,但也许从那时起 Swift 世界中的某些东西已经得到改进。还是我真的在当前 Swift 的功能中遇到了硬性限制?

she*_*lll 5

在终于找到了解决方案,问题出在铸造上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()`!")
    }
}
Run Code Online (Sandbox Code Playgroud)

和一个示例 VC:

class MyGenericViewController: BaseMvpViewController<MyModel, MyView, MyPresenter>, MyView {
    ...
    override func createPresenter() -> MainPresenter {
        return MyPresenter()
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

这个 VC 将自动拥有一个viewModeltype 属性MyModel(可以是任何东西,例如结构、类、枚举等),presentertype属性MyPresenter和这个 Presenter 将自动附加在viewDidLoad和之间viewWillAppear。必须覆盖一种方法createPresenter(),您必须在其中创建并返回演示者。这在自定义 VC 的viewDidLoad方法之前调用。演示者在deinit.

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

init() {
    ...
    MyGenericViewController.load()
    ...
}
Run Code Online (Sandbox Code Playgroud)

编辑 1: 我发现在这个 SO 答案中将通用 VC 加载到 Objective-C 运行时/sf/answers/3072778131/

编辑 2: 示例演示者类。强制性的东西是typealias, theweak 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
}
Run Code Online (Sandbox Code Playgroud)


归档时间:

查看次数:

2789 次

最近记录:

7 年,10 月 前