[RxSwift๐Ÿฆˆ] #4 RxCocoa, MVVM ์ •๋ฆฌ

๋˜์ƒยท2022๋…„ 1์›” 12์ผ
2

iOS

๋ชฉ๋ก ๋ณด๊ธฐ
7/47
post-thumbnail

RxSwift 3๊ต์‹œ ์—์„œ ์ด๋ฏธ ์‚ฌ์šฉํ•œ ๊ฒƒ๋“ค์ด์ง€๋งŒ, ๊ธ€์ด ๊ธธ์–ด์ ธ์„œ 3๊ต์‹œ ์ •๋ฆฌ๋„ ๊ฐ™์ด ์ง„ํ–‰ํ•˜๋ ค๊ณ  ํ•œ๋‹ค. MVC, MVP, MVVM ๊ตฌ์กฐ ์—์„œ ๊ณต๋ถ€ํ•  ๋•Œ๋Š” ๋””์ž์ธ ํŒจํ„ด์˜ ๊ฐœ๋…๋งŒ ์–ผ์ถ” ์ดํ•ด๊ฐ€ ๋˜๊ณ , ์ฝ”๋“œ๋Š” ์ž˜ ์ดํ•ด๊ฐ€ ์•ˆ๊ฐ”์—ˆ๋Š”๋ฐ ์„ค๋ช…์„ ๋“ฃ๊ณ  ๋ณด๋‹ˆ ์ดํ•ด๊ฐ€ ์ž˜ ๊ฐ€์„œ ๋†€๋ž๋‹ค.

3. UI Component์™€ ์—ฐ๋™ ์ •๋ฆฌ

1. Subject

https://reactivex.io/documentation/subject.html

Data Control

  • Observable์€ ๋Ÿฐํƒ€์ž„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์„œ ์ˆ˜์ •๋  ์ˆ˜ ์žˆ์ง€ ์•Š๋‹ค.
  • ์™ธ๋ถ€ ์ปจํŠธ๋กค์— ์˜ํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ์ˆ˜๋„ subscribe ํ•  ์ˆ˜๋„ ์žˆ๋Š” ๊ฒƒ์ด ํ•„์š”ํ•œ๋ฐ, ๊ทธ๊ฒŒ Subject ๋‹ค.
  • Subject์—๋Š” 4๊ฐ€์ง€ ์ข…๋ฅ˜๊ฐ€ ์žˆ๋Š”๋ฐ,
  • PublishSubject
    • ๊ธฐ๋ณธ์ ์ธ subject
    • ์—ฌ๋Ÿฌ ๊ฐ์ฒด๊ฐ€ subscribe ํ•  ์ˆ˜ ์žˆ์Œ.
    • PublishSubject์— ๋ฐ์ดํ„ฐ๊ฐ€ ์ƒ์„ฑ๋˜๋ฉด ๋ชจ๋“  ๊ตฌ๋…์ž์—๊ฒŒ ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณด๋‚ด์ค€๋‹ค.
  • BehaviorSubject
    • ๊ธฐ๋ณธ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์‹œ์ž‘ํ•œ๋‹ค.
    • ๋ˆ„๊ตฐ๊ฐ€ subscribe ํ•˜์ž๋งˆ์ž ๊ธฐ๋ณธ ๊ฐ’์„ ๋ณด๋‚ด์ค€๋‹ค.
    • ์ƒˆ๋กœ์šด ๊ฐ์ฒด๊ฐ€ subscribe ํ•˜๋ฉด ๊ฐ€์žฅ ์ตœ๊ทผ์˜ data๋ฅผ ๋ณด๋‚ด์ค€๋‹ค.
  • AsyncSubject
    • ์—ฌ๋Ÿฌ ๊ฐ์ฒด๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ๋…ํ•˜๋”๋ผ๋„ complete ๋˜๋Š” ์‹œ์ ์˜ ๊ฐ€์žฅ ์ตœ๊ทผ ๋ฐ์ดํ„ฐ๋งŒ ๋ณด๋‚ด์ค€๋‹ค.
  • ReplaySubject
    • ๊ตฌ๋…์ž์—๊ฒŒ ์ˆœ์„œ๋Œ€๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ ค์ค€๋‹ค.
    • ์ƒˆ๋กœ์šด ๊ตฌ๋…์ž๊ฐ€ ์ƒ๊ธฐ๋ฉด ์—ฌํƒœ๊นŒ์ง€ ์ „๋‹ฌ๋œ ๋ชจ๋“  ๊ฒƒ์„ ํ•œ๋ฒˆ์— ๋‚ด๋ ค์ค€๋‹ค.

Hot Observable / Cold Observable

  • Hot Observable : ์ƒ์„ฑ๋˜์ž๋งˆ์ž ํ•ญ๋ชฉ์„ ๋ฐฐ์ถœ. ๊ตฌ๋…์ž๋Š” ํ•ญ๋ชฉ์ด ๋ฐฐ์ถœ๋˜๋Š” ์ค‘๊ฐ„๋ถ€ํ„ฐ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • Cold Observable : Observer๊ฐ€ ๊ตฌ๋…ํ•  ๋•Œ ๊นŒ์ง€ ํ•ญ๋ชฉ์„ ๋ฐฐ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค. Observer๋Š” ์ด Observable ์ด emit ํ•˜๋Š” ํ•ญ๋ชฉ ์ „์ฒด๋ฅผ ๊ตฌ๋…ํ•  ์ˆ˜ ์žˆ๋‹ค.

2. RxCocoa

RxSwift์˜ ๊ธฐ๋Šฅ์„ UIKit์— Extension์œผ๋กœ ์ถ”๊ฐ€ํ•œ ๊ฒƒ. syntatic sugar๋ผ๊ณ  ๋ณด๋ฉด ๋œ๋‹ค.

UI ์ž‘์—…์˜ ํŠน์ง•

  • main thread ์—์„œ๋งŒ ๋Œ์•„๊ฐ„๋‹ค.
    • `observeOn(MainScheduler.instance)๋Š” ๋ฌด์กฐ๊ฑด ๋“ค์–ด๊ฐ€์•ผ ํ•œ๋‹ค.
  • ์ž‘์—…์„ ํ•˜๋‹ค๊ฐ€ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋ฉด stream ์ด ๋Š์–ด์ ธ์„œ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.
    • .catchErrorJustReturn("") ๊ฐ™์€ ๊ฒƒ์œผ๋กœ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋„ ์•ฑ์ด ์ฃฝ์ง€ ์•Š๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผํ•œ๋‹ค.

Observable / Driver

  • observeOn().catchErrorJustReturn("").bind/subscribe ๋ฅผ chaining ํ•œ ์ฝ”๋“œ๋ฅผ
  • asDrive(onErrorJustReturn: "").drive(itemCountLabel.rx.text) ์ด๋Ÿฐ ์‹์œผ๋กœ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค.
  • ์—๋Ÿฌ๊ฐ€ ์ƒ๊ธฐ๋ฉด ""์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ณ , drive ๋Š” ์•Œ์•„์„œ main thread ์—์„œ ๋Œ์•„๊ฐ€๋Š” ์ฝ”๋“œ.

Subject / Relay

  • Subject ์—ญ์‹œ UI ์ž‘์—…์„ ํ•  ๋•Œ๋Š” ๋Š์–ด์ง€์ง€ ์•Š๊ฒŒ ํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค. -> Subject ๋Œ€์‹  Relay ๋ฅผ ์‚ฌ์šฉํ•˜์ž. error ๊ฐ€ ๋‚˜๋ฉด ๊ทธ๋ƒฅ ๋ฌด์‹œํ•˜๊ณ  ์ง„ํ–‰ํ•ด์ค€๋‹ค.
    • ๊ทผ๋ฐ error ๊ฐ€ ๋‚˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์–˜๊ธฐ๋Š”? onError ๋Š” ์—†๊ณ  onCompleted ๋งŒ ์žˆ๋‹ค๋Š” ๋ง๊ณผ ๊ฐ™๋‹ค.
    • relay ๋Š” self.menuObservable.accept($0) ์œผ๋กœ ์˜ค๋กœ์ง€ ์ •๋ณด๋ฅผ ๋ฐ›์•„๋“ค์ผ ์ˆ˜๋งŒ ์žˆ๋‹ค.




4. MVVM ์ ์šฉํ•˜๊ธฐ

domain์—์„œ ์˜ค๋Š” ๋ชจ๋ธ์€ ๋Œ€๋ถ€๋ถ„ ์šฐ๋ฆฌ ํ™”๋ฉด์—์„œ ๋ณด์ด๋Š” ๋ชจ๋ธ๊ณผ ๋‹ค๋ฅด๋‹ค. -> 3๊ต์‹œ์— ํ•œ ๊ฒƒ ์ฒ˜๋Ÿผ ๋งตํ•‘ํ•ด์„œ ์จ์•ผํ•จ.

1. MVC

  • Controller(UIViewController) Input์„ Controller ๊ฐ€ ๋ฐ›๋Š”๋‹ค.
  • Controller ๊ฐ€ View ํ•œํ…Œ view setting, updateUI ๋“ฑ์„ ์‹œํ‚จ๋‹ค.
  • update์˜ ๊ธฐ๋ฐ˜์€ ๋ชจ๋ธ์ด ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. UIKit์— ์ข…์†๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— Test ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋‹จ์ 
    • Controller ๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ์ˆ˜๊ฐ€ ์—†์Œ.. ํ…Œ์ŠคํŠธ ํ•˜์ž๊ณ  ๋กœ์ง๋งŒ ๋”ฐ๋กœ ๋–ผ์–ด๋‚ด๊ธฐ๋„ ์• ๋งค... ๊ธฐ๋Šฅ์ ์œผ๋กœ๋Š” ์ด๋ ‡๊ฒŒ ๋‚˜๋ˆ„๋Š”๊ฒŒ ์ข‹์•˜๋Š”๋ฐ...!
    • ํด๋ผ์ด์–ธํŠธ ๋‹จ ๋‚ด์—์„œ๋งŒ ๋‚˜๋ˆ ์„œ ๊ฐœ๋ฐœํ•˜๋ ค๋‹ค๋ณด๋‹ˆ ์–ด๋ ต๋‹ค.




2. MVP

  • MVC๋ฅผ ๋ณด์™„ํ•˜๋ฉด์„œ, Controller ์˜ ์—ญํ• ์„ ์ œํ•œํ•˜๋Š” ๊ตฌ์กฐ.
  • UIViewController ๋Š” View ์—ญํ• ๋งŒ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•œ๋‹ค. (test ํ•  ํ•„์š”๊ฐ€ ์—†๊ฒŒ. ๊ฒฐ๊ตญ ์‚ฌ์šฉ์ž ์ž…๋ ฅ์€ MVP์˜ View ๋กœ ๋ฐ›๋Š” ๊ฒƒ)
  • ๊ด€๋ จ๋œ ๋กœ์ง ๋ถ€๋ถ„๋งŒ ์ „๋ถ€ Presenter ๋กœ ๋บ€๋‹ค. ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๊ณ  View์— ๊ทธ๋ฆฌ๋ผ์ฝ” ์‹œํ‚จ๋‹ค.
  • View ํ•˜๋‚˜๋ฅผ Presenter ํ•˜๋‚˜๊ฐ€ ๋‹ด๋‹นํ•œ๋‹ค (1:1 ๊ตฌ์กฐ)
  • Presentor ๋Š” test ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋‹จ์ 
    • View ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•  ๋•Œ๋งˆ๋‹ค presenter ์— ๋‹ค ๋ฌผ์–ด๋ด์•ผ ํ•˜๊ณ , Preseter ๋Š” ์ „๋ถ€ ๊ทธ๋ ค์„œ View ํ•œํ…Œ ๋ณด์—ฌ์ฃผ๋ผ๊ณ  ์‹œ์ผœ์•ผ ํ•œ๋‹ค. -> ๋ฒˆ๊ฑฐ๋กœ์›€..
    • 1:1 ๊ด€๊ณ„๋ผ ๋น„์Šทํ•œ View๋ฅผ ๋ณด์—ฌ์ฃผ๋Š”๋ฐ๋„ VP ์Œ์ด ์—ฌ๋Ÿฌ๊ฐœ๊ฐ€ ๋œ๋‹ค.




3. MVVM

  • MVP ํŒจํ„ด์˜ ๋‹จ์ ์„ ๊ทน๋ณตํ•˜๊ธฐ ์œ„ํ•ด presenter ๋ฅผ ๊ณตํ†ต์œผ๋กœ (ViewModel) ์‚ฌ์šฉ.
  • viewModel ์€ View ์— ๋ณด์—ฌ์ค„ ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๊ณ , View ํ•œํ…Œ ์ง์ ‘์ ์œผ๋กœ ๋ญ˜ ๊ทธ๋ฆฌ๋ผ๊ณ  ์ง€์‹œํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • viewModel : view = 1 : n -> ๋น„์Šทํ•œ ๊ตฌ์กฐ์—์„œ ๊ฐ™์€ viewModel ์„ ๊ฐ€์ง€๊ณ , ํ™”๋ฉด์—๋งŒ ๋‹ค๋ฅด๊ฒŒ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  • Data Binding : viewModel ์„ ๋ฐ”๋ผ๋ณด๋‹ค๊ฐ€ ๊ฐ’์ด ๋ฐ”๋€Œ๋ฉด ์ฝ์–ด์™€์„œ view ์— ๋‚˜ํƒ€๋‚ธ๋‹ค. ๊ผญ ์ „์žฌํ•˜์ง„ ์•Š๋Š”๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด์„œ SwiftUI ์—์„œ๋Š” needUpdateUI ๋ฅผ ๋งŒ๋“ค๊ณ , ํ™”๋ฉด์— ํ‘œํ˜„๋  ๋ณ€์ˆ˜์— didSet { needUpdateUI() } viewDidLoad ์—์„œ viewModel.needUpdateUI = { ์—…๋ฐ์ดํŠธ ํ•ด์•ผ ํ•˜๋Š” viewModel ์š”์†Œ ๋„ฃ๊ธฐ } ์ „๋‹ฌํ•ด์ค˜์„œ ํ™”๋ฉด์— ๋ณด์ด๋Š” ์š”์†Œ๋ฅผ ๋ฐ”๊พธ๊ธฐ๋„ ํ•œ๋‹ค.
  • ํ•˜๋‚˜์˜ view์— ๋Œ€ํ•œ viewModel ์„ ๋”ฐ๋กœ ๋‘˜ ์ˆ˜๋„ ์žˆ๋‹ค.
// MenuListViewModel
var cellViewModels: [CellViewModel] = []


// MenuVC > viewDidLoad
viewModel.menuObservable
    .bind(to: tableView.rx.items(cellIdentifier: cellId, cellType: MenuItemTableViewCell.self)) { index, item, cell in
        let cellViewModel = self.viewModel.cellViewModels[index]
        cell.setViewModel(cellViewModel)
        
        cell.onChange = { [weak self] increase in
            self?.viewModel.changeCount(item: item, increase: increase)
        }
    }
    .disposed(by: disposeBag)




// MenuItemTableViewCell
var disposeBag = DisposeBag()

func setViewModel(viewModel: CellViewModel) {
    viewModel.title.bind(title.rx.text)
//    cell.title.text = item.name
//    cell.price.text = "\(item.price)"
//    cell.count.text = "\(item.count)"
}

// ๋Œ€์‹  TableView, CollectionView ๊ฐ™์€ ๊ฒƒ์€, Cell ์ด reuse ๋˜๊ธฐ ๋•Œ๋ฌธ์— 
// ์•„๊นŒ ๊ฐ€์ง€๊ณ  ์žˆ๋˜ viewModel์„ ๊ณ„์† ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉด ์•ˆ๋จ.
override func prepareForReuse() {
    disposeBag = DisposeBag() // ์ƒˆ๋กœ ๋งŒ๋“ค์–ด์„œ ์ด์ „์— ๊ฐ–๊ณ  ์žˆ๋˜ viewModel ๋‚ ๋ฆฌ๊ธฐ
}




์ถœ์ฒ˜

์œ ํŠœ๋ธŒ ๊ณฐํŠ€๊น€๋‹˜ RxSwift 4์‹œ๊ฐ„์— ๋๋‚ด๊ธฐ
https://youtu.be/iHKBNYMWd5I

RxSwift 4์‹œ๊ฐ„์— ๋๋‚ด๊ธฐ github
https://github.com/iamchiwon/RxSwift_In_4_Hours

profile
0๋…„์ฐจ iOS ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค.

1๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2022๋…„ 1์›” 14์ผ

์‹ฑ๊ธฐํ•œ๊ฑธ ๊ณต๋ถ€ํ•˜์‹œ๋Š”๊ตฐ์š”!

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ