clamp
Clamp
clamp
글쓰기 관리
전체 방문자
오늘
어제
  • 분류 전체보기 (509)
    • IOS (85)
    • SwiftUI+TCA+Combine (9)
    • RxSwift + MVVM (56)
    • Clean Architecture (12)
    • SWIFT (56)
    • iOS - TDD (2)
    • 디자인패턴 (4)
    • CS (56)
      • 알고리즘 (29)
      • 운영체제 (15)
      • 자료구조 (2)
      • 네트워킹 (4)
      • 기타 (6)
    • 회고 (0)
    • Firebase (18)
    • SwiftUI (10)
    • iOS - UIKit (11)
    • iOS - 오픈소스 (6)
    • 코딩테스트 (166)
      • 프로그래머스 (164)
    • 정보처리기사 (14)
    • GitHub (2)
글쓰기 / 관리자

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • Q
  • Swift
  • uikit
  • ㅅ

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
clamp

Clamp

RxSwift + MVVM 곰튀김님
RxSwift + MVVM

RxSwift + MVVM 곰튀김님

2023. 4. 5. 21:44

유튜브 곰튀김님의 영상을 보고 정리.

https://www.youtube.com/watch?v=iHKBNYMWd5I&list=PL03rJBlpwTaBrhux_C8RmtWDI_kZSLvdQ&index=2 


Subject

Subject: 옵저버블처럼 구독할 수 있고, 옵저버블 밖에서 값을 받아 데이터를 컨트롤해서, 새로운 값을 집어넣어 줄 수 있는것.

 

PublishSubject

구독이 시작되고 전달되는 이벤트를 전달한다.

 

BehaviorSubject

 기본값을 가지고 시작한다. 구독을 시작한 시점에 데이터가 전달이 안되었다면 기본값을 내려주고, 새로운 구독자가 생기면 가장 최근에 전달된 값을 전달한다.

 

AsyncSubject

데이터가 전달되고, 여러 구독자가 생기더라도 데이터를 내려보내주지 않는다. Complete되는 시점에 가장 마지막에 전달된 데이터를 전달하고 종료한다.

 

ReplaySubject

구독이 시작되고 전달된 데이터를 전달한다. 새로운 구독자가 추가되면 현재까지 전달된 데이터들을 모두 전달한다. 그 이후에 발생하는 데이터는 Publish와 같이 동일하게 전달한다.

 

가장 많이 사용되는건 Publish와 Behavior이다.

 

옵저버블이 메모리에서 정리되는건 Completed, Error이벤트가 발생했을 시기이다.

그러므로 take(1)처럼 이벤트를 한 번 받고 dispose되는 옵저버가 있다면 순환참조는 일어나지만 메모리 릭은 발생하지 않는다.

 

MVVM패턴

ViewModel

  • 어떤 데이터를 보여줘야하느냐, 데이터에 대해 어떤 처리를 해줘야하는지는 뷰모델에 알고있다.
  • 데이터의 모든 변경은 뷰모델이 처리한다. count를 감소시킨다던지, 초기화를 한다던지.. 이런 부분에 대한 처리는 ViewModel이 담당한다.
  • 화면에 어떤 내용을 보여줘야하는지는 모두 ViewModel이 가지고 있다. View를 위한 Model이기 때문에 ViewModel이다.
  • 모든 생각과 데이터에 대한 처리는 ViewModel이 하고, View는 데이터에대해 아는것이 아무것도 없어야 한다.
  • 만약 버그가 발생해 테스트케이스를 만든다면 ViewController를 테스트하는게 쉬을까, ViewModel을 만드는게 쉬울까. ViewController를 상속받은 ViewController가 아닌 ViewModel을 만드는게 쉬울것이다.
  • Controller에는 데이터를 처리하는 로직이 전혀 없기 때문에 데이터의 오류로 인해 버그가 발생할 일이 없다.
class MenuListViewModel{
    
    // Subject 옵저버블처럼 값을 방출도하지만 옵저버처럼 값을 받을 수도 있다.
    var menuObservable = BehaviorSubject<[Menu]>(value: [])
    
    let disposeBag = DisposeBag()
    
    
    lazy var itemsCount = menuObservable.map{
        $0.map{ $0.count }.reduce(0, +)
    }
    
    lazy var totalPrice = menuObservable.map{
        $0.map{ $0.price * $0.count }.reduce(0, +)
    }
    
    init(){
        let menus: [Menu] = [
            Menu(id: 1, name: "튀김1", price: 100, count: 0),
            Menu(id: 2, name: "튀김1", price: 100, count: 0),
            Menu(id: 3, name: "튀김1", price: 100, count: 0),
            Menu(id: 4, name: "튀김1", price: 100, count: 0)
        ]
        
        menuObservable.onNext(menus)
    }
    
    func clearAllItemSelections(){
        menuObservable
            .map{ menus in
                return menus.map{
                    Menu(id: $0.id, name: $0.name, price: $0.price, count: 0)
                }
            }
            .take(1)
            .subscribe(onNext: {
                self.menuObservable.onNext($0)
            })
            .disposed(by: disposeBag)
    }
    
    func changeCount(_ item: Menu, _ increse: Int){
        menuObservable
            .map{ menus in
                return menus.map{
                    if $0.id == item.id{
                        return Menu(id: $0.id, name: $0.name, price: $0.price, count: max($0.count + increse, 0))
                    } else {
                        return Menu(id: $0.id, name: $0.name, price: $0.price, count: $0.count)
                    }
                }
            }
            .take(1)
            .subscribe(onNext: {
                self.menuObservable.onNext($0)
            })
            .disposed(by: disposeBag)
    }
    
    func onOrder(){
        
    }
}

 

Controller

  • Controller에는 뷰의 요소만 담게된다.
  • View에 어떻게 보여지게 될지, 이 요소들을 잡아서 어떤 형태로 화면에 뿌릴지만 지정한다. 어떤 로직을 담아야 하는지는 ViewModel이 가지고있다. 
  • 모든 데이터에 대한 처리는 ViewModel이 하게된다. 그러므로 Controller는 Model을 알 수 없다.
class MenuViewController: UIViewController {
    
    let viewModel = MenuListViewModel()
    let disposeBag = DisposeBag()
    
    // MARK: - Life Cycle

    override func viewDidLoad() {
        super.viewDidLoad()
        
        viewModel.menuObservable
            .bind(to: tableView.rx.items(cellIdentifier: cellID, cellType: MenuItemTableViewCell.self)){ index, item, cell in
                cell.title.text = item.name
                cell.price.text = "\(item.count)"
                cell.count.text = "\(item.count)"
                
                cell.onChange = { [weak self] increse in
                    self?.viewModel.changeCount(item, increse)
                }
            }
            .disposed(by: disposeBag)
        
        viewModel.itemsCount
            .map{ "\($0)"}
            .observeOn(MainScheduler.instance)
            .bind(to: itemCountLabel.rx.text)
            .disposed(by: disposeBag)
        
        viewModel.totalPrice
            .map{ $0.currencyKR() }
            .observeOn(MainScheduler.instance)
            .bind(to: totalPrice.rx.text)
            .disposed(by: 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() {
        viewModel.clearAllItemSelections()
    }

    @IBAction func onOrder(_ sender: UIButton) {
        // TODO: no selection
        // showAlert("Order Fail", "No Orders")
//        performSegue(withIdentifier: "OrderViewController", sender: nil)
        
        viewModel.onOrder()
        
    }
}
저작자표시 비영리 동일조건 (새창열림)
    'RxSwift + MVVM' 카테고리의 다른 글
    • [Rx + MVVM] Input과 Output을 사용한 MVVM
    • RxSwift - Single, Maybe, Completable
    • [iOS]. MVVM 정리
    • RxSwift, MVVM - RxMemo(2)
    clamp
    clamp
    주니어 iOS개발자의 발악!!!!!!!

    티스토리툴바