使用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进行通信。