使用MVP模式iOS将数据传递到另一个控制器 [英] Passing data to another controller using MVP pattern iOS

查看:105
本文介绍了使用MVP模式iOS将数据传递到另一个控制器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用MVP设计模式.我有两种将数据传递到另一个视图控制器的方式,我将在下面提到.我不知道其中哪个是正确的,并且没有违反MVP模式.我知道这是一个很大的问题,但这确实非常重要.

I am using MVP design pattern. I have two ways of passing data to the another view controller which I'll mention below. I don't know which of them is correct and doesn't violate MVP pattern. I know this is very big question but it's really very important.

1)下面使用init with presenter通过传递视图控制器所需的演示者来创建视图控制器.

1) Using init with presenter, below am creating the view controller by passing the presenter which the view controller needs.

struct HotelTemplate {
    var id: String
    var name: String
    var icon: String
}

class ListHotelPresenter: NSObject {

    private var data = [HotelTemplate]()

    func getPresenter(_ index: Int) -> HotelDetailsPresenter {
        let presenter = HotelDetailsPresenter(id: data[index].id, name: data[index].name, icon: data[index].icon)
        return presenter
    }
}

// InitialViewController
class ListHotelViewController: UIViewController {

    class func `init`(with presenter: ListHotelPresenter) -> ListHotelViewController {
        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ListHotelViewController") as! ListHotelViewController
        vc.presenter = presenter
        return vc
    }

    var presenter: ListHotelPresenter!

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let detailPresenter = presenter.getPresenter(indexPath.row)
        let vc = HotelDetailsViewController.init(with: detailPresenter)
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

// ViewController that will be push
class HotelDetailsViewController: UIViewController {

    class func `init`(with presenter: HotelDetailsPresenter) -> HotelDetailsViewController {
        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HotelDetailsViewController") as! HotelDetailsViewController
        return vc
    }

    var presenter: HotelDetailsPresenter!

    override func viewDidLoad() {
        super.viewDidLoad()

        presenter.loadHoteData()
    }
}

class HotelDetailsPresenter: NSObject {
    var hotelId: String
    var hotelName: String
    var hotelIcon: String

    init(id: String, name: String, icon: String) {
        self.hotelId = id
        self.hotelName = name
        self.hotelIcon = icon
    }

    func loadHoteData() {
        // Load hotel data.
        // Alamofire.request ..................
    }
}

2)通过发送id, name, icon,然后在viewDidLoad()

2) By sending the id, name, icon and then initialising the presenter in the viewDidLoad()

struct HotelTemplate {
    var id: String
    var name: String
    var icon: String
}

class ListHotelPresenter: NSObject {

    private var data = [HotelTemplate]()

    func getHotelName(_ index: Int) -> String {
        return data[index].name
    }

    func getHotelIcon(_ index: Int) -> String {
        return data[index].icon
    }

    func getHotelId(_ index: Int) -> String {
        return data[index].id
    }
}

class ListHotelViewController: UIViewController {

    var presenter: ListHotelPresenter!

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "HotelDetailsViewController") as! HotelDetailsViewController
        vc.id = presenter.getHotelId(indexPath.row)
        vc.name = presenter.getHotelName(indexPath.row)
        vc.icon = presenter.getHotelIcon(indexPath.row)
        self.navigationController?.pushViewController(vc, animated: true)
    }
}

class HotelDetailsViewController: UIViewController {

    var presenter: HotelDetailsPresenter!
    var id = ""
    var name = ""
    var icon = ""

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter = HotelDetailsPresenter(id: id, name: name, icon: icon)
        presenter.loadHoteData()
    }
}

class HotelDetailsPresenter: NSObject {
    var hotelId: String
    var hotelName: String
    var hotelIcon: String

    init(id: String, name: String, icon: String) {
        self.hotelId = id
        self.hotelName = name
        self.hotelIcon = icon
    }

    func loadHoteData() {
        // Load hotel data.
        // Alamofire.request ..................
    }
}

以下是我的担忧:

1)哪个是正确的? (我觉得第一种方法确实很干净,但我的上级告诉我,这违反了MVP模式.我不知道如何.)

1) Which one is correct? (I feel the first method is really very clean, but my senior told me that it violates the MVP pattern. I don't know how.)

2)控制器的演示者属性应该是公共的还是私有的?

2) Should the presenter property of the controller be public or private?

推荐答案

在Objective-C中,您可以拥有一个可以传递模型的视图.我们可以在视图控制器的头文件中向前声明一个模型:

In Objective-C, you can have a View that passes a Model around. We could forward-declare a model in the view controller's header file:

@class HotelTemplate;

在.m文件中,请确保不要"#import HotelTemplate.h".这样,模型仍然不透明.您可以将其传递出去,但不能向内看.

In the .m file, I'd make sure not to "#import HotelTemplate.h". This way, the model remained opaque. You can pass it around, but you can't look inside.

我不知道有什么方法可以在Swift中强制执行.因此,让我按照您的示例并通过下一个演示者,而不是下一个模型.我们需要的是一种在viewDidLoad()中的每个演示者上设置视图"的方法.为了防止保留周期,这将是一个较弱的属性.

I don't know of any way to enforce this in Swift. So let me follow your example and pass the next presenter, instead of the next model. All we need is a way to set the View on the each presenter in viewDidLoad(). To prevent retain cycles, this will be a weak property.

首先,这是列表视图控制器.我做了一个UITableViewController.

First, here's the list view controller. I made it a UITableViewController.

final class ListHotelViewController: UITableViewController {
    private var presenter = ListHotelPresenter()

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter.setView(self)
        presenter.loadHotelData()
    }
}

演示者将通过协议将其回调:

The presenter will call it back through a protocol:

protocol ListHotelView: class {
    func redraw()
    func showDetails(nextPresenter: HotelDetailsPresenter)
}

extension ListHotelViewController: ListHotelView {
    func redraw() {
        tableView.reloadData()
    }

    func showDetails(nextPresenter: HotelDetailsPresenter) {
        let vc = HotelDetailsViewController.init(with: nextPresenter)
        navigationController?.pushViewController(vc, animated: true)
    }
}

这是表视图的数据源和委托:

Here's the table view's data source and delegate:

extension ListHotelViewController /* UITableViewDataSource */ {
    public override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return presenter.hotelCount
    }

    public override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Hotel", for: indexPath) as! HotelTableViewCell
        presenter.configure(cell: cell, row: indexPath.row)
        return cell
    }
}

extension ListHotelViewController /* UITableViewDelegate */ {
    public override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        presenter.showDetails(row: indexPath.row)
    }
}

在每个步骤中,它都向Presenter提出.演示者有一个薄弱的链接返回到视图,但只能通过协议.不知道View是ListHotelViewController.对于基于终端的接口,我们应该能够使用一堆print(_)语句来实现View.

At each step, it defers to the Presenter. The Presenter has a weak link back to the View, but only through the protocol. It has no idea that the View is a ListHotelViewController. We should be able to implement the View with a bunch of print(_) statements instead, for a terminal-based interface.

final class ListHotelPresenter {
    private weak var view: ListHotelView?
    private var model: [HotelTemplate] = [] {
        didSet {
            view?.redraw()
        }
    }

    var hotelCount: Int {
        return model.count
    }

    func setView(_ view: ListHotelView) {
        self.view = view
    }

    func loadHotelData() {
        // Network request to load data into model. Let's pretend with dummy data:
        let hilton = HotelTemplate(id: "hilton", name: "Hilton", icon: "H")
        let radisson = HotelTemplate(id: "radisson", name: "Radisson", icon: "R")
        model = [hilton, radisson]
    }

    func configure(cell: HotelCell, row: Int) {
        let hotel = model[row]
        cell.show(name: hotel.name, icon: hotel.icon)
    }

    func showDetails(row: Int) {
        let nextPresenter = HotelDetailsPresenter(summaryModel: model[row])
        view?.showDetails(nextPresenter: nextPresenter)
    }
}

configure(cell:row:)中,演示者与给定的单元格对话.请注意,单元格也是协议.借助MVP,我真的尝试想象如何使用它来创建基于终端的界面.这是单元格:

In configure(cell:row:), the Presenter talks to the given cell. Note that the cell is also a protocol. With MVP, I really try to imagine how I'd use it to make a terminal-based interface. Here's the cell:

protocol HotelCell: class {
    func show(name: String, icon: String)
}

final class HotelTableViewCell: UITableViewCell {}

extension HotelTableViewCell: HotelCell {
    func show(name: String, icon: String) {
        textLabel?.text = name
        // Do something to show icon
    }
}

实际上,您可以向表格视图单元格中添加更多内容.在此示例中,我只使用了一个普通单元格及其文本标签.

In practice, you'd add more to the table view cell. I just used a plain cell and its text label for this example.

最后,我们来到推送视图控制器.

Finally, we come to the pushed view controller.

final class HotelDetailsViewController: UIViewController {
    private var presenter: HotelDetailsPresenter!
    @IBOutlet private var textLabel: UILabel!

    static func `init`(with presenter: HotelDetailsPresenter) -> HotelDetailsViewController {
        let vc = UIStoryboard(name: "Main", bundle: nil)
                .instantiateViewController(withIdentifier: "HotelDetailsViewController")
                as! HotelDetailsViewController
        vc.presenter = presenter
        return vc
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter.setView(self)
        presenter.show()
    }
}

我假设我们将立即显示所拥有的摘要信息,但是从Web服务中可以获取更多细节.这是由这个演示者完成的.

I'm making the assumption that we'll immediately show the summary information we have, but that there's more to detail to come from a web service. That's done by this Presenter.

struct HotelDetails {
    let location: String
    // more details…
}

final class HotelDetailsPresenter {
    private weak var view: HotelDetailsView?
    private let summaryModel: HotelTemplate        
    private var detailsModel: HotelDetails? {
        didSet {
            guard let detailsModel = detailsModel else { return }
            view?.showDetails(location: detailsModel.location)
        }
    }

    init(summaryModel: HotelTemplate) {
        self.summaryModel = summaryModel
    }

    func setView(_ view: HotelDetailsView) {
        self.view = view
    }

    func show() {
        view?.show(name: summaryModel.name, icon: summaryModel.icon)
        // Network request to load data into detailsModel
    }
}

与往常一样,Presenter告诉View通过协议执行的操作:

As usual, the Presenter tells the View what to do through a protocol:

protocol HotelDetailsView: class {
    func show(name: String, icon: String)
    func showDetails(location: String)
}

extension HotelDetailsViewController: HotelDetailsView {
    func show(name: String, icon: String) {
        textLabel?.text = name
        // Do something to show icon
    }

    func showDetails(location: String) {
        // Show other hotel details we loaded
    }
}

如您所见,这些属性是私有的.为了支持单元测试,我们可能需要使用private(set)放宽它,以便仅设置器是私有的.

As you can see, the properties are private. To support unit testing, we might need to relax that using private(set) so that only the setters are private.

这篇关于使用MVP模式iOS将数据传递到另一个控制器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆