テーブルビューを含むビューコントローラーがあるので、テーブルビューのデータソースとデリゲートをどこに置くべきか、それが外部オブジェクトなのか、それともVIPERパターンと言えばビューコントローラーに書き込むことができるのかを確認したいと思います。
通常、パターンを使用してこれを行います:
ViewDidLoadで、self.presenter.showSongs()
のようなプレゼンターにいくつかのフローを要求します
Presenterにはインタラクターが含まれており、showSongsメソッドでは次のようなインタラクターにデータを要求します:self.interactor.loadSongs()
曲がView Controllerに戻る準備ができたら、Presenterをもう一度使用して、このデータをView Controllerに表示する方法を決定します。しかし、私の質問はテーブルビューのデータソースで何をすべきですか?
まず、ビューはPresenterからのデータを要求するべきではありません-これはVIPERアーキテクチャの違反です。
ビューはパッシブです。プレゼンターがコンテンツを表示するのを待つ。プレゼンターにデータを要求することはありません。
あなたの質問については:すべてのデータを含め、現在のビューステートをPresenterに保持することをお勧めします。状態に基づいてVIPERパーツ間の通信を提供しているためです。
ただし、他の方法では、PresenterはUIKitについて何も認識していないため、UITableViewDataSourceおよびUITableViewDelegateはビューレイヤーの一部である必要があります。
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
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
発表者
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 Interactor
- (void)getSongs {
[self.service getSongsWithCompletionHandler:^(NSArray *songs) {
[self.presenter didLoadSongs:songs];
}];
}
@end
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
であり、プレゼンターにデータを要求するべきではありません。したがって、self.presenter.showSongs()
をself.presenter.onViewDidLoad()
に置き換えます。
2)プレゼンターの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 (サンプルプロジェクトが含まれています)
非常に良い質問@Matrosov。まず最初にお伝えしたいのは、View、Controller、Interactor、Presenter、RoutingなどのVIPERコンポーネント間の責任の分離についてです。
それは、開発中の時間とともに変化する味に関するものです。 MVC、MVVP、MVVMなど、多くのアーキテクチャパターンが世の中に存在します。味が変わると、MVCからVIPERに変わります。誰かがMVVPからVIPERに変わります。
クラスのサイズを行数を少なくして、サウンドビジョンを使用します。データソースメソッドをViewController自体に保持するか、UITableViewDatasoruceプロトコルに準拠するカスタムオブジェクトを作成できます。
ビューコントローラーをスリムに保ち、すべてのメソッドとクラスを単一責任の原則に従うという私の目標。
Viperは、凝集性が高く、結合度の低いソフトウェアの作成に役立ちます。
この開発モデルを使用する前に、クラス間の責任の分散について十分に理解しておく必要があります。
Oopsとプロトコル)の基本を理解したら、このモデルはMVCと同じくらい簡単です。
答えと私の異なる点は次のとおりです。
1、ViewはPresenterに何かを要求することはありません。Viewは、events(viewDidLoad()/refresh()/loadMore()/generateCell()
)をPresenterに渡すだけでよく、PresenterはViewが渡したイベントに応答します。
2、私はインタラクターがプレゼンターへの参照を持つべきではないと思います、プレゼンターはコールバック(ブロックまたはクロージャー)を介してインターアクターと通信します。
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
}
1つはUITableViewCellのデータの塗りつぶし用、もう1つはユーザーが行をタップしたときのコールバック用の2つのタイプエイリアスを宣言します。