맛보기의 마지막으로, 간단하게 프로젝트에 적용해보자. MVVM과 찰떡인 RxSwift를 적용해보면서 아키텍쳐와 사용방법에 대해 익숙해져보자.
PublishSubject
라는 녀석이다.
import Foundation
import RxSwift
class MenuListViewModel {
var menuObservable = PublishSubject<[Menu]>()
lazy var itemsCount = self.menuObservable.map {
$0.map { $0.count }.reduce(0, +)
}
lazy var totalPrice = self.menuObservable.map {
$0.map { $0.price & $0.count }.reduce(0, +)
}
init() {
let menus: [Menu] = [
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0),
]
self.menuObservable.onNext(menus)
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.itemsCount
.map { "\($0)" }
.subscribe(onNext: { [weak self] in
self.totalPrice.text = $0
})
.disposed(by: self.disposeBag)
self.viewModel.totalPrice
.map { $0.currencyKR() }
.subscribe(onNext: {
self.totalPrice.text = $0
})
.disposed(by: self.disposeBag)
}
RxCocoa
라는 프레임워크를 사용하게 되면, 보다 쉽게 처리가 가능하다.self.viewModel.itemsCount
.map { "\($0)" }
.bind(to: itemCountLabel.rx.text)
.disposed(by: self.disposeBag)
[weak self]
를 사용해주어야 한다.behavierSubject
를 사용해야 한다.import Foundation
import RxSwift
class MenuListViewModel {
var menuObservable = BehaviorSubject<[Menu]>(value: [])
lazy var itemsCount = self.menuObservable.map {
$0.map { $0.count }.reduce(0, +)
}
lazy var totalPrice = self.menuObservable.map {
$0.map { $0.price * $0.count }.reduce(0, +)
}
init() {
let menus: [Menu] = [
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0),
]
self.menuObservable.onNext(menus)
}
func clearAllItemSelections() {
self.menuObservable
.map { menus in
return menus.map {
Menu(name: $0.name, price: $0.price, count: 0)
}
}
.take(1) // 만약에 이게 없으면 연관관계가 만들어져서, 값이 변경될 때마다 호출되서 count가 0으로 고정되어 있을 것임
.subscribe(onNext: {
self.menuObservable.onNext($0)
})
}
}
import UIKit
import RxCocoa
import RxSwift
class MenuViewController: UIViewController {
// MARK: - Life Cycle
let viewModel = MenuListViewModel()
var disposeBag = DisposeBag()
let cellID = "MenuItemTableViewCell"
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.menuObservable
.bind(to: tableView.rx.items(cellIdentifier: self.cellID, cellType: MenuItemTableViewCell.self)) { index, item, cell in
cell.title.text = item.name
cell.price.text = "\(item.price)"
cell.count.text = "\(item.count)"
}
.disposed(by: self.disposeBag)
self.viewModel.itemsCount
.map { "\($0)" }
.bind(to: self.itemCountLabel.rx.text)
.disposed(by: self.disposeBag)
self.viewModel.totalPrice
.map { $0.currencyKR() }
.bind(to: self.totalPrice.rx.text)
.disposed(by: self.disposeBag)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let identifier = segue.identifier ?? ""
if identifier == "OrderViewController",
let orderVC = segue.destination as? OrderViewController {
// TODO: pass selected menus
}
}
func showAlert(_ title: String, _ message: String) {
let alertVC = UIAlertController(title: title, message: message, preferredStyle: .alert)
alertVC.addAction(UIAlertAction(title: "OK", style: .default))
present(alertVC, animated: true, completion: nil)
}
// MARK: - InterfaceBuilder Links
@IBOutlet var activityIndicator: UIActivityIndicatorView!
@IBOutlet var tableView: UITableView!
@IBOutlet var itemCountLabel: UILabel!
@IBOutlet var totalPrice: UILabel!
@IBAction func onClear() {
self.viewModel.clearAllItemSelections()
}
@IBAction func onOrder(_ sender: UIButton) {
// TODO: no selection
// showAlert("Order Fail", "No Orders")
// performSegue(withIdentifier: "OrderViewController", sender: nil)
self.viewModel.menuObservable.onNext([
Menu(name: "changed", price: Int.random(in: 100...1000), count: Int.random(in: 0...10)),
Menu(name: "changed", price: Int.random(in: 100...1000), count: Int.random(in: 0...10)),
Menu(name: "changed", price: Int.random(in: 100...1000), count: Int.random(in: 0...10)),
])
}
}
{: .center-small}MVC
{: .center-small}MVP
{: .center-small}MVVM
self.viewModel.itemsCount
.map { "\($0)" }
.catchErrorJustReturn("")
.observeOn(MainScheduler.instance)
.bind(to: self.itemCountLabel.rx.text)
.disposed(by: self.disposeBag)
catchErrorJustReturn
observeOn
bind
driver
라는 것을 만들었다.driver
는 항상 main thread에서 동작한다.self.viewModel.totalPrice
.map { $0.currencyKR() }
.asDriver(onErrorJustReturn: "")
.drive(itemCountLabel.rx.text)
.disposed(by: self.disposeBag)
RxRelay
import RxRelay
var menuObservable = BehaviorRelay<[Menu]>(value: [])
func clearAllItemSelections() {
_ = self.menuObservable
.map { menus in
return menus.map { menu in
Menu(id: menu.id, name: menu.name, price: menu.price, count: 0)
}
}
.take(1) // 만약에 이게 없으면 연관관계가 만들어져서, 값이 변경될 때마다 호출되서 count가 0으로 고정되어 있을 것임
.subscribe(onNext: {
self.menuObservable.accept($0)
})
}