Share
Embed
MVC→MVP→MVVM→Fluxの実装の違いを比較してみる
iOSDC 2017 9/17 13:50 TrackB
https://iosdc.jp/2017/node/1396
iOSDesignPatternSamples
https://github.com/marty-suzuki/iOSDesignPatternSamples
FluxCapacitory
https://github.com/marty-suzuki/FluxCapacitor
Flux & MVVM (Brooklyn Swift Developers)
https://speakerdeck.com/martysuzuki/flux-and-mvvm-brooklyn-swift-developers
補足資料
【iOSDC2017】MVC→MVP→MVVM→Fluxの実装の違いを比較してみる
https://qiita.com/marty-suzuki/items/5a4f680b10bb82501aa3
Taiki Suzuki
September 17, 2017
Transcript
54 slides
-
final class FavoriteViewController: UIViewController, UITableViewDelegate { @IBOutlet weak var tableView:
UITableView! ... override func viewDidLoad() { super.viewDidLoad() tableView.delegate = self ... } ... private func showRepository(with repository: Repository) { let vc = RepositoryViewController(repository: repository, favoriteModel: favoriteModel) navigationController?.pushViewController(vc, animated: true) } ... func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let repository = favoriteModel.favorites[indexPath.row] showRepository(with: repository) } } -
final class RepositoryViewController: SFSafariViewController { ... private let repository: Repository
private let favoriteModel: FavoriteModel init(repository: Repository, favoriteModel: FavoriteModel) { self.repository = repository self.favoriteModel = favoriteModel super.init(url: repository.url, entersReaderIfAvailable: true) } ... } -
final class FavoriteModel { private(set) var favorites: [Repository] = []
{ didSet { delegate?.favoriteDidChange?() } } weak var delegate: FavoriteModelDelegate? func addFavorite(_ repository: Repository) { favorites.append(repository) } func removeFavorite(_ repository: Repository) { favorites.remove(at: index) } } -
@objc protocol FavoriteModelDelegate: class { @objc optional func favoriteDidChange() }
final class FavoriteViewController: UIViewController, FavoriteModelDelegate { @IBOutlet weak var tableView: UITableView! let favoriteModel = FavoriteModel() ... override func viewDidLoad() { super.viewDidLoad() ... favoriteModel.delegate = self ... } ... func favoriteDidChange() { tableView.reloadData() } } -
final class RepositoryViewController: SFSafariViewController { private let favoriteModel: FavoriteModel private
let repository: Repository private(set) lazy var favoriteButtonItem: UIBarButtonItem ... @objc private func favoriteButtonTap(_ sender: UIBarButtonItem) { if favoriteModel.favorites.index(where: { $0.url == repository.url }) == nil { favoriteModel.addFavorite(repository) ... } else { favoriteModel.removeFavorite(repository) ... } } } -
protocol FavoriteView: class { ... func showRepository(with repository: Repository) }
final class FavoriteViewController: UIViewController, FavoriteView { @IBOutlet weak var tableView: UITableView! private(set) lazy var presenter: FavoritePresenter = FavoriteViewPresenter(view: self) ... func showRepository(with repository: Repository) { let vc = RepositoryViewController(repository: repository, favoritePresenter: presenter) navigationController?.pushViewController(vc, animated: true) } } -
final class RepositoryViewController: SFSafariViewController { ... private let presenter: RepositoryPresenter
init(repository: Repository, favoritePresenter: FavoritePresenter) { self.presenter = .init(repository: repository, favoritePresenter: favoritePresenter) super.init(url: repository.url, entersReaderIfAvailable: true) } ... } -
protocol FavoritePresenter: class { ... func addFavorite(_ repository: Repository) func
removeFavorite(_ repository: Repository) func contains(_ repository: Repository) -> Bool } -
final class FavoriteViewPresenter: FavoritePresenter { private weak var view: FavoriteView?
private var favorites: [Repository] = [] { didSet { view?.reloadData() } } ... func addFavorite(_ repository: Repository) { if contains(repository) { return } favorites.append(repository) } func removeFavorite(_ repository: Repository) { guard let index = index(of: repository) else { return } favorites.remove(at: index) } private func index(of repository: Repository) -> Int? { return favorites.lazy.index { $0.url == repository.url } } func contains(_ repository: Repository) -> Bool { return index(of: repository) != nil } } -
protocol FavoriteView: class { func reloadData() ... } final class
FavoriteViewController: UIViewController, FavoriteView { @IBOutlet weak var tableView: UITableView! private(set) lazy var presenter: FavoritePresenter = .init(view: self) ... func reloadData() { tableView?.reloadData() } } -
final class RepositoryViewController: SFSafariViewController { private(set) lazy var favoriteButtonItem: UIBarButtonItem
... private let presenter: RepositoryPresenter init(repository: Repository, favoritePresenter: FavoritePresenter, entersReaderIfAvailable: Bool = true) { self.presenter = .init(repository: repository, favoritePresenter: favoritePresenter) super.init(url: repository.url, entersReaderIfAvailable: entersReaderIfAvailable) self.presenter.view = self } ... @objc private func favoriteButtonTap(_ sender: UIBarButtonItem) { presenter.favoriteButtonTap() } } -
protocol RepositoryPresenter: class { init(repository: Repository, favoritePresenter: FavoritePresenter) weak var
view: RepositoryView? { get set } ... func favoriteButtonTap() } -
final class RepositoryViewPresenter: RepositoryPresenter { weak var view: RepositoryView? private
let favoritePresenter: FavoritePresenter private let repository: Repository ... init(repository: Repository, favoritePresenter: FavoritePresenter) { self.repository = repository self.favoritePresenter = favoritePresenter } ... func favoriteButtonTap() { if favoritePresenter.contains(repository) { favoritePresenter.removeFavorite(repository) ... } else { favoritePresenter.addFavorite(repository) ... } } } -
final class FavoriteViewModel { let relaodData: Observable<Void> ... private let
_favorites = Variable<[Repository]>([]) private let disposeBag = DisposeBag() ... init(favoritesObservable: Observable<[Repository]>, ...) { ... self.relaodData = _favorites.asObservable().map { _ in } favoritesObservable.bind(to: _favorites).disposed(by: disposeBag) } ... } -
final class FavoriteViewController: UIViewController { ... var favoritesInput: AnyObserver<[Repository]> {
return favorites.asObserver() } var favoritesOutput: Observable<[Repository]> { return viewModel.favorites } private let favorites = PublishSubject<[Repository]>() private let disposeBag = DisposeBag() private private(set) lazy var viewModel: FavoriteViewModel = { .init(favoritesObservable: self.favorites, ...) }() ... private var showRepository: AnyObserver<Repository> { return UIBindingObserver(UIElement: self) { me, repository in let vc = RepositoryViewController(repository: repository, favoritesOutput: me.favoritesOutput, favoritesInput: me.favoritesInput) me.navigationController?.pushViewController(vc, animated: true) }.asObserver() } ... } -
final class RepositoryViewController: SFSafariViewController { private let favoriteButtonItem: UIBarButtonItem private
let viewModel: RepositoryViewModel ... init(repository: Repository, favoritesOutput: Observable<[Repository]>, favoritesInput: AnyObserver<[Repository]>) { let favoriteButtonItem = UIBarButtonItem(title: nil, style: .plain, target: nil, action: nil) self.favoriteButtonItem = favoriteButtonItem self.viewModel = RepositoryViewModel(repository: repository, favoritesOutput: favoritesOutput, favoritesInput: favoritesInput, favoriteButtonTap: favoriteButtonItem.rx.tap) super.init(url: repository.url, entersReaderIfAvailable: true) } ... } -
final class RepositoryViewModel { private let disposeBag = DisposeBag() ...
init(repository: Repository, favoritesOutput: Observable<[Repository]>, favoritesInput: AnyObserver<[Repository]>, favoriteButtonTap: ControlEvent<Void>) { let favoritesAndIndex: Observable<([Favorites], Int?)> = favoritesOutput .map { [repository] favorites in (favorites, favorites.index(where: { $0.url == repository.url })) } ... favoriteButtonTap .withLatestFrom(favoritesAndIndex) .map { [repository] favorites, index in var favorites = favorites if let index = index { favorites.remove(at: index) return favorites } favorites.append(repository) return favorites } .subscribe(onNext: { favoritesInput.onNext($0) }).disposed(by: disposeBag) } } -
final class FavoriteViewController: UIViewController { @IBOutlet weak var tableView: UITableView!
... private let disposeBag = DisposeBag() private private(set) lazy var viewModel: FavoriteViewModel ... override func viewDidLoad() { super.viewDidLoad() ... viewModel.relaodData.bind(to: reloadData).disposed(by: disposeBag) } ... private var reloadData: AnyObserver<Void> { return UIBindingObserver(UIElement: self) { me, _ in me.tableView.reloadData() }.asObserver() } } -
final class RepositoryAction: Actionable { typealias DispatchValueType = Dispatcher.Repository ...
func selectRepository(_ repository: Repository) { invoke(.selectedRepository(repository)) } ... } extension Dispatcher { enum Repository: DispatchValue { case selectedRepository(GithubKit.Repository?) ... } } -
final class RepositoryStore: Storable { typealias DispatchValueType = Dispatcher.Repository ...
let selectedRepository: Observable<Repository?> var selectedRepositoryValue: Repository? { return _selectedRepository.value } private let _selectedRepository = Variable<Repository?>(nil) ... init(dispatcher: Dispatcher) { self.selectedRepository = _selectedRepository.asObservable() ... register { [weak self] in switch $0 { case .selectedRepository(let value): self?._selectedRepository.value = value ... } } } } -
final class FavoriteViewController: UIViewController { ... private let store: RepositoryStore
= .instantiate() private let action = RepositoryAction() ... private var showRepository: AnyObserver<Void> { return UIBindingObserver(UIElement: self) { me, _ in guard let vc = RepositoryViewController() else { return } me.navigationController?.pushViewController(vc, animated: true) }.asObserver() } ... } -
final class RepositoryViewController: SFSafariViewController { private let action: RepositoryAction private
let store: RepositoryStore ... init?() { self.store = .instantiate() self.action = .init() guard let repository = store.selectedRepositoryValue else { return nil } super.init(url: repository.url, entersReaderIfAvailable: true) } override func viewDidLoad() { super.viewDidLoad() let repository = store.selectedRepository.filter { $0 != nil }.map { $0! } ... } ... } -
final class RepositoryAction: Actionable { typealias DispatchValueType = Dispatcher.Repository ...
func clearSelectedRepository() { invoke(.selectedRepository(nil)) } func addFavorite(_ repository: Repository) { invoke(.addFavorite(repository)) } func removeFavorite(_ repository: Repository) { invoke(.removeFavorite(repository)) } } extension Dispatcher { enum Repository: DispatchValue { ... case addFavorite(GithubKit.Repository) case removeFavorite(GithubKit.Repository) case removeAllFavorites } } -
final class RepositoryStore: Storable { typealias DispatchValueType = Dispatcher.Repository ...
let favorites: Observable<[Repository]> var favoritesValue: [Repository] { return _favorites.value } private let _favorites = Variable<[Repository]>([]) ... init(dispatcher: Dispatcher) { self.favorites = _favorites.asObservable() ... register { [weak self] in switch $0 { ... case .addFavorite(let value): if self?._favorites.value.index(where: { $0.url == value.url }) == nil { self?._favorites.value.append(value) } case .removeFavorite(let value): if let index = self?._favorites.value.index(where: { $0.url == value.url }) { self?._favorites.value.remove(at: index) } case .removeAllFavorites: self?._favorites.value.removeAll() } } } } -
final class RepositoryViewController: SFSafariViewController { private let favoriteButtonItem = .init(title:
nil, style: .plain, target: nil, action: nil) private let disposeBag = DisposeBag() private let action: RepositoryAction private let store: RepositoryStore ... override func viewDidLoad() { super.viewDidLoad() let repository = store.selectedRepository.filter { $0 != nil }.map { $0! } let containsRepository = Observable.combineLatest(repository, store.favorites) { repo, favs in (favs.contains { $0.url == repo.url }, repo) } favoriteButtonItem.rx.tap .withLatestFrom(containsRepository) .subscribe(onNext: { [weak self] contains, repository in if contains { self?.action.removeFavorite(repository) } else { self?.action.addFavorite(repository) } }) .disposed(by: disposeBag) } ... } -
final class FavoriteViewController: UIViewController { @IBOutlet weak var tableView: UITableView!
private let dataSource = FavoriteViewDataSource() private let disposeBag = DisposeBag() private let store: RepositoryStore = .instantiate() private let action = RepositoryAction() override func viewDidLoad() { super.viewDidLoad() ... store.favorites.map { _ in } .bind(to: reloadData) .disposed(by: disposeBag) } ... private var reloadData: AnyObserver<Void> { return UIBindingObserver(UIElement: self) { me, _ in me.tableView.reloadData() }.asObserver() } }