使用VIPER与UITableView的iOS

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

通常使用模式我这样做:

在viewDidLoad中,我请求一些来自演示者的stream,比如self.presenter.showSongs()

Presenter包含交互器和showSongs方法我要求交互的一些数据,如:self.interactor.loadSongs()

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

首先你的视图不应该问Presenter的数据 – 这是违反VIPER架构。

视图是被动的。 它等待演示者给它的内容显示; 它从不要求演示者提供数据。

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

但是Presenter不应该知道任何有关UIKit的知识,所以UITableViewDataSource和UITableViewDelegate应该是View层的一部分。

为了使ViewController保持良好的状态并以“SOLID”方式进行,最好将DataSource和Delegate保存在不同的文件中。 但是这些部分仍然应该知道主持人要求数据。 所以我更喜欢在ViewController的扩展中做到这一点

所有模块应该看起来像这样:

视图

ViewController.h

 extern NSString * const TableViewCellIdentifier; @interface ViewController @end 

ViewController.m

 NSString * const TableViewCellIdentifier = @"CellIdentifier"; @implemntation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self.presenter setupView]; } - (void)refreshSongs { [self.tableView reloadData]; } @end 

视图控制器+ TableViewDataSource.h

 @interface ViewController (TableViewDataSource) <UITableViewDataSource> @end 

视图控制器+ 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 

视图控制器+ TableViewDelegate.h

 @interface ViewController (TableViewDelegate) <UITableViewDelegate> @end 

视图控制器+ 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 

主持人

Presenter.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 

交互器

Interactor.m

 @implementation Presenter - (void)getSongs { [self.service getSongsWithCompletionHandler:^(NSArray *songs) { [self.presenter didLoadSongs:songs]; }]; } @end 

非常好的问题@Matrosov。 首先我要告诉你的是,VIPER组件之间的责任分离,比如View,Controller,Interactor,Presenter,Routing。

更多的是在开发过程中尝到一种变化。 有像MVC,MVVP,MVVM等许多架构模式。随着时间的推移,当我们的口味变化,我们从MVC更改为VIPER。 有人从MVVP转到VIPER。

通过保持class级规模较小,使用您的声音视觉。 您可以在ViewController本身中保存数据源方法或者创build一个符合UITableViewDatasoruce协议的自定义对象。

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

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

在使用这种发展模式之前,应该充分理解课堂上责任分配。

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

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 } } 

1)首先,View是passive的,不应该要求Presenter提供数据。 所以,用self.presenter.showSongs()replaceself.presenter.onViewDidLoad()

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

3)在你的Presenter上,在onSongsDataFetched()的实现上,你应该按照View所要求的格式来准备数据,然后调用self.view.showSongs(listOfSongs)

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

5)你的TableViewDataSource将运行你的数组mySongs并填充TableView。

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

创build一个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 } 

声明两个关于UITableViewCell中的填充数据的callback的typealias,另一个用于当用户点击一行时的callback。

这里是我从答案的不同点:

1,View不应该问Presenter的东西,View只需要将事件( viewDidLoad()/refresh()/loadMore()/generateCell() )传递给Presenter,而Presenter响应哪个事件传递给View。

2,我不认为Interactor应该有一个Presenter的引用,Presenter通过callback(block或闭包)与Interactor进行通信。