23

我有一个包含表视图的视图控制器,所以我想问我应该把表视图数据源和委托放在哪里,它应该是一个外部对象还是我可以在我的视图控制器中写它,如果我们说 VIPER 模式。

通常使用模式我这样做:

在 viewDidLoad 我向演示者请求一些流程,例如self.presenter.showSongs()

Presenter 包含交互器,并且在 showSongs 方法中我从交互器请求一些数据,例如:self.interactor.loadSongs()

当歌曲准备好传回视图控制器时,我再次使用演示者来确定该数据应如何在视图控制器中显示。但是我的问题是我应该如何处理表格视图的数据源?

4

6 回答 6

22

首先,您的视图不应向 Presenter 询问数据——这违反了 VIPER 架构。

视图是被动的。它等待 Presenter 给它显示内容;它从不向 Presenter 询问数据。

至于你的问题:最好在 Presenter 中保持当前视图状态,包括所有数据。因为它提供基于状态的 VIPER 部件之间的通信。

但另一方面,Presenter 不应该对 UIKit 有任何了解,所以 UITableViewDataSource 和 UITableViewDelegate 应该是 View 层的一部分。

为了让您的 ViewController 保持良好状态并以“SOLID”方式进行,最好将 DataSource 和 Delegate 保存在单独的文件中。但是这些部分还是要知道presenter问数据。所以我更喜欢在 ViewController 的扩展中做

所有模块应如下所示:

看法

视图控制器.h

extern NSString * const TableViewCellIdentifier;

@interface ViewController
@end

视图控制器.m

NSString * const TableViewCellIdentifier = @"CellIdentifier";

@implemntation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   [self.presenter setupView];
}

- (void)refreshSongs {
   [self.tableView reloadData];
}

@end

ViewController+TableViewDataSource.h

@interface ViewController (TableViewDataSource) <UITableViewDataSource>
@end

ViewController+TableViewDataSource.m

@implementation ItemsListViewController (TableViewDataSource)
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [self.presenter songsCount];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
   UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

   Song *song = [self.presenter songAtIndex:[indexPath.row]];
   // Configure cell

   return cell;
}
@end

ViewController+TableViewDelegate.h

@interface ViewController (TableViewDelegate) <UITableViewDelegate>
@end

ViewController+TableViewDelegate.m

@implementation ItemsListViewController (TableViewDelegate)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    Song *song = [self.presenter songAtIndex:[indexPath.row]];
    [self.presenter didSelectItemAtIndex:indexPath.row];
}
@end

主持人

演示者.m

@interface Presenter()
@property(nonatomic,strong)NSArray *songs;
@end

@implementation Presenter
- (void)setupView {
  [self.interactor getSongs];
}

- (NSUInteger)songsCount {
   return [self.songs count];
}

- (Song *)songAtIndex:(NSInteger)index {
   return self.songs[index];
}

- (void)didLoadSongs:(NSArray *)songs {
   self.songs = songs;
   [self.userInterface refreshSongs];
}

@end

交互者

交互者.m

@implementation Interactor
- (void)getSongs {
   [self.service getSongsWithCompletionHandler:^(NSArray *songs) {
      [self.presenter didLoadSongs:songs];
    }];
}
@end
于 2016-08-04T12:36:43.407 回答
11

Swift 3.1中的示例,可能对某人有用:

看法

class SongListModuleView: UIViewController {

    // MARK: - IBOutlets

    @IBOutlet weak var tableView: UITableView!


    // MARK: - Properties

    var presenter: SongListModulePresenterProtocol?


    // MARK: - Methods

    override func awakeFromNib() {
        super.awakeFromNib()

        SongListModuleWireFrame.configure(self)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        presenter?.viewWillAppear()
    }
}

extension SongListModuleView: SongListModuleViewProtocol {

    func reloadData() {
        tableView.reloadData()
    }
}

extension SongListModuleView: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return presenter?.songsCount ?? 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "SongCell", for: indexPath) as? SongCell, let song = presenter?.song(atIndex: indexPath) else {
            return UITableViewCell()
        }

        cell.setupCell(withSong: song)

        return cell
    }
}

主持人

class SongListModulePresenter {
    weak var view: SongListModuleViewProtocol?
    var interactor: SongListModuleInteractorInputProtocol?
    var wireFrame: SongListModuleWireFrameProtocol?
    var songs: [Song] = []
    var songsCount: Int {
        return songs.count
    }
}

extension SongListModulePresenter: SongListModulePresenterProtocol {

    func viewWillAppear() {
        interactor?.getSongs()
    }

    func song(atIndex indexPath: IndexPath) -> Song? {
        if songs.indices.contains(indexPath.row) {
            return songs[indexPath.row]
        } else {
            return nil
        }
    }
}

extension SongListModulePresenter: SongListModuleInteractorOutputProtocol {

    func reloadSongs(songs: [Song]) {
        self.songs = songs
        view?.reloadData()
    }
}

交互者

class SongListModuleInteractor {
    weak var presenter: SongListModuleInteractorOutputProtocol?
    var localDataManager: SongListModuleLocalDataManagerInputProtocol?
    var songs: [Song] {
        get {
            return localDataManager?.getSongsFromRealm() ?? []
        }
    }
}

extension SongListModuleInteractor: SongListModuleInteractorInputProtocol {

    func getSongs() {
        presenter?.reloadSongs(songs: songs)
    }
}

线框

class SongListModuleWireFrame {}

extension SongListModuleWireFrame: SongListModuleWireFrameProtocol {

    class func configure(_ view: SongListModuleViewProtocol) {
        let presenter: SongListModulePresenterProtocol & SongListModuleInteractorOutputProtocol = SongListModulePresenter()
        let interactor: SongListModuleInteractorInputProtocol = SongListModuleInteractor()
        let localDataManager: SongListModuleLocalDataManagerInputProtocol = SongListModuleLocalDataManager()
        let wireFrame: SongListModuleWireFrameProtocol = SongListModuleWireFrame()

        view.presenter = presenter
        presenter.view = view
        presenter.wireFrame = wireFrame
        presenter.interactor = interactor
        interactor.presenter = presenter
        interactor.localDataManager = localDataManager
    }
}
于 2017-08-16T09:13:02.217 回答
5

1)首先,View是passive一个不应该为Presenter询​​问数据的东西。所以,替换self.presenter.showSongs()self.presenter.onViewDidLoad()

2) 在您的 Presenter 上,在您的实现上,onViewDidLoad()您通常应该调用交互器来获取一些数据。然后交互器将调用,例如,self.presenter.onSongsDataFetched()

3)在您的演示者上,onSongsDataFetched()您应该按照视图所需的格式准备数据,然后调用self.view.showSongs(listOfSongs)

4)在您的视图上,在执行时showSongs(listOfSongs),您应该设置self.mySongs = listOfSongs然后调用tableView.reloadData()

5)您的 TableViewDataSource 将在您的数组上运行mySongs并填充 TableView。

有关 VIPER 架构的更多高级技巧和有用的良好实践,我推荐这篇文章:https ://www.ckl.io/blog/best-practices-viper-architecture (包括示例项目)

于 2017-04-11T01:48:44.483 回答
4

很好的问题@Matrosov。首先我想告诉你,这都是关于 VIPER 组件之间的职责分离,例如 View、Controller、Interactor、Presenter、Routing。

它更多的是关于口味在开发过程中随着时间的推移而发生的变化。有许多架构模式,例如 MVC、MVVP、MVVM 等。随着时间的推移,当我们的口味发生变化时,我们会从 MVC 变为 VIPER。有人从 MVVP 变为 VIPER。

通过保持班级人数较少的行数来使用您的声音视觉。您可以将数据源方法保留在 ViewController 本身中,或者创建一个符合 UITableViewDatasoruce 协议的自定义对象。

我的目标是让视图控制器保持苗条,并且每个方法和类都遵循单一职责原则。

Viper 有助于创建高度内聚和低耦合的软件。

在使用这种开发模式之前,应该对类之间的责任分配有一个很好的理解。

一旦你对 iOS 中的 Oops 和协议有了基本的了解。你会发现这个模型和 MVC 一样简单。

于 2016-08-03T11:02:26.713 回答
0

创建一个 NSObject 类并将其用作自定义数据源。在此类中定义您的委托和数据源。

 typealias  ListCellConfigureBlock = (cell : AnyObject , item : AnyObject? , indexPath : NSIndexPath?) -> ()
    typealias  DidSelectedRow = (indexPath : NSIndexPath) -> ()
 init (items : Array<AnyObject>? , height : CGFloat , tableView : UITableView? , cellIdentifier : String?  , configureCellBlock : ListCellConfigureBlock? , aRowSelectedListener : DidSelectedRow) {

    self.tableView = tableView

    self.items = items

    self.cellIdentifier = cellIdentifier

    self.tableViewRowHeight = height

    self.configureCellBlock = configureCellBlock

    self.aRowSelectedListener = aRowSelectedListener


}

声明两个 typealias 用于回调,一个用于在 UITableViewCell 中填充数据,另一个用于当用户点击一行时。

于 2016-08-01T14:22:11.263 回答
0

以下是我与答案的不同观点:

1、View永远不应该向Presenter询​​问某事,View只需将events( viewDidLoad()/refresh()/loadMore()/generateCell())传递给Presenter,Presenter响应View传递给哪些事件。

2、我不认为Interactor应该有Presenter的引用,Presenter通过回调(块或闭包)与Interactor通信。

于 2017-11-08T14:47:53.547 回答