Bibi's DevLog ๐Ÿค“๐ŸŽ

[๊ณฐํŠ€๊น€๋‹˜] RxSwift + MVVM ์ •๋ฆฌ ๋ณธ๋ฌธ

๐Ÿ“ฑ๐ŸŽ iOS

[๊ณฐํŠ€๊น€๋‹˜] RxSwift + MVVM ์ •๋ฆฌ

๋น„๋น„ bibi 2022. 6. 6. 18:49

[๊ณฐํŠ€๊น€๋‹˜] RxSwift + MVVM ์ •๋ฆฌ

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

1๊ต์‹œ

JSON ๋‹ค์šด๋กœ๋“œ ์ž‘์—…์ด ๋™๊ธฐ์ ์œผ๋กœ ๊ตฌํ˜„์ด ๋˜์–ด ์žˆ๋‹ค.

LOAD ํด๋ฆญ ์‹œ ํƒ€์ด๋จธ๋ฅผ ํฌํ•จํ•ด ํ™”๋ฉด ์ „์ฒด๊ฐ€ ๋ฉˆ์ถ”๊ณ , ๋‹ค์šด๋กœ๋“œ๊ฐ€ ๋๋‚˜๋ฉด ๋‹ค์‹œ ํ™”๋ฉด์ด ์›€์ง์ธ๋‹ค.

๋น„๋™๊ธฐ๋กœ ๋งŒ๋“ค์–ด๋ณด์ž.

๋น„๋™๊ธฐ๋ž€?

: ํ˜„์žฌ ์Šค๋ ˆ๋“œ์—์„œ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ, ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๋ฅผ ์ด์šฉํ•ด ๋‹ค๋ฅธ ์ž‘์—…์„ ๋™์‹œ์— ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ.

๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•œ ์ž‘์—…์ด ์™„๋ฃŒ๋˜๋ฉด, ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ›์•„์™€์•ผ ํ•œ๋‹ค.

  • DispatchQueue.global().async { ... }
  • ์ด๋ ‡๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋ฉด ํ”„๋กœ๊ทธ๋žจ์ด ์ฃฝ๋Š”๋‹ค.
  • UI๋ฅผ ๊ฑด๋“œ๋ฆฌ๋Š” ๋ถ€๋ถ„์€ global()์Šค๋ ˆ๋“œ๊ฐ€ ์•„๋‹Œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋กœ ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ.
  • UI๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ๋‹ค์‹œ ํ•œ ๋ฒˆ DispatchQueue.main.async { ... }

completion handler

์ˆœ์ˆ˜ swift๋กœ ๋น„๋™๊ธฐ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ›์•„์˜ค๋ ค๋ฉด completion @escapimg ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์จ์•ผ ํ•œ๋‹ค - ํด๋กœ์ € ๋ฐฉ์‹

  • @escaping ํด๋กœ์ €๋ฅผ ํ†ตํ•ด, ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ์ฒ˜๋ฆฌ๋œ ๋น„๋™๊ธฐ ์ž‘์—… ๊ฒฐ๊ณผ๋ฅผ ๋„˜๊ฒจ์ค€๋‹ค
  • ๋‹จ์  : ๋น„๋™๊ธฐ๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ๊ฒน์ณ ์จ์•ผ ํ•˜๋Š” ์ƒํ™ฉ ๋ฐœ์ƒ ์‹œ ์•Œ์•„๋ณด๊ธฐ ๋ณต์žกํ•ด์ง„๋‹ค(depth๊ฐ€ ๊นŠ์–ด์ง„๋‹ค)

๊ทธ๋ž˜์„œ ๋น„๋™๊ธฐ ๊ฒฐ๊ณผ๊ฐ’์„ ํด๋กœ์ €๊ฐ€ ์•„๋‹ˆ๋ผ, ๋ฆฌํ„ด๊ฐ’์œผ๋กœ ํ‘œํ˜„ํ•˜๋ ค๋Š” ์‹œ๋„๊ฐ€ ์ƒ๊ฒผ๋‹ค.

๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ

  • ๋น„๋™๊ธฐ์ž‘์—… ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฆฌํ„ด๊ฐ’์œผ๋กœ ๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ<์ œ๋„ค๋ฆญ>์œผ๋กœ ๋ฐ˜ํ™˜
  • ๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ<์ œ๋„ค๋ฆญ>.๋‚˜์ค‘์—์ƒ๊ธฐ๋ฉด{...}์—์„œ ๋‚˜์ค‘์— ์ƒ๊ธฐ๋ฉด ํ•  ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  • class ๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ<T> {
        private let task: (@escaping (T) -> Void) -> Void
    
        init(task: @escaping (@escapint (T) -> Void) -> Void) {
            self.task = task
        } // ๋ฐ์ดํ„ฐ ์ƒ์„ฑ์‹œ ํด๋กœ์ €๋ฅผ ๋ฐ›์•„ ๊ฐ€์ง€๊ณ  ์žˆ์Œ
    
        func ๋‚˜์ค‘์—์˜ค๋ฉด(_ f: @escaping (T) -> Void) {
            task(f)
        } // ๋‚˜์ค‘์—์˜ค๋ฉด ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ์‹œ, ๊ฐ€์ง€๊ณ  ์žˆ๋˜ ํด๋กœ์ €๋ฅผ ์‹คํ–‰
    }
  • ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉ
    • // ๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ ๋งŒ๋“ค๊ธฐ
      func downloadJson(_ url: String) -> ๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ<String?> {
          return ๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ() { f in
               DispatchQueue.global().async {
                   let url = URL(string: url)!
                   let data = try! Data(contentsOf: url)
                   let json = String(data: data, encoding: .utf8)
      
                   DispatchQueue.main.async {
                       f(json)
                   }
               }
          }
      }
      
      // ๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ ๋ฅผ ์‚ฌ์šฉํ•  ๋–„ : .๋‚˜์ค‘์—์˜ค๋ฉด ์œผ๋กœ ์‚ฌ์šฉ
      func onLoad() {
          let json: ๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ<String?> = downloadJson(URL)
      
          json.๋‚˜์ค‘์—์˜ค๋ฉด { json in
              self.editView.text = json
              // ... 
          }
      }

RxSwift

  • ์œ„์™€ ๊ฐ™์€ ๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ๋กœ ๋น„๋™๊ธฐ์ž‘์—… ๊ฒฐ๊ณผ๊ฐ’์„ ์ „๋‹ฌํ•˜๋Š” ์œ ํ‹ธ๋ฆฌํ‹ฐ ์ค‘์— ํ•˜๋‚˜๊ฐ€ RxSwift์ด๋‹ค.
    • ๋‚˜์ค‘์—์ƒ๊ธฐ๋Š”๋ฐ์ดํ„ฐ<ํƒ€์ž…> = Observable<ํƒ€์ž…>
    • .๋‚˜์ค‘์—์ƒ๊ธฐ๋ฉด = .subscribe
  • ์ฆ‰ RxSwift์˜ ์šฉ๋„๋„ "๋น„๋™๊ธฐ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฆฌํ„ด๊ฐ’์œผ๋กœ ์ฃผ๊ธฐ ์œ„ํ•ด ๋งŒ๋“  ์œ ํ‹ธ๋ฆฌํ‹ฐ"์ด๋‹ค.
  • Disposable
    • .dispose() : ๋ฒ„๋ฆฐ๋‹ค๋Š” ๋œป. ๋™์ž‘์„ ์ทจ์†Œ์‹œํ‚ด.
      • ์ด ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด Observable ์ž‘์—…์ด ๋๋‚˜๋“  ๋๋‚˜์ง€ ์•Š์•˜๋“  ์ฆ‰์‹œ ์ž‘์—…์„ ์ทจ์†Œ์‹œํ‚จ๋‹ค.
  • ์ˆœํ™˜์ฐธ์กฐ
    • ์ˆœํ™˜์ฐธ์กฐ๋Š” ์™œ ์ƒ๊ธฐ๋Š”๊ฐ€?
      • ํด๋กœ์ €๊ฐ€ ์ž์‹  ๋‚ด๋ถ€์— ์„ ์–ธ๋œ self๋ฅผ ์บก์ฒ˜ํ•˜๋ฉด์„œ ๋ ˆํผ๋Ÿฐ์Šค ์นด์šดํŠธ๊ฐ€ ์ฆ๊ฐ€ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ˆœํ™˜์ฐธ์กฐ๊ฐ€ ์ƒ๊ธด๋‹ค.
      • ํ•ด๊ฒฐ1 : ํด๋กœ์ €์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ [weak self]๋กœ ๋งŒ๋“ค๊ณ , self๋ฅผ self?๋กœ ์„ ์–ธํ•ด ํ•ด๊ฒฐ ๊ฐ€๋Šฅ
      • ํ•ด๊ฒฐ2 : ํด๋กœ์ €๋ฅผ ์ œ๋•Œ ์—†์• ๊ธฐ
        • Rx์˜ ๊ฒฝ์šฐ .completed/ .error/.disposed๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํด๋กœ์ €๊ฐ€ ์‚ฌ๋ผ์ง„๋‹ค.

RxSwift ์‚ฌ์šฉ๋ฐฉ๋ฒ•

  • ๋น„๋™๊ธฐ๋กœ ์ƒ๊ธฐ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ Observable๋กœ ๊ฐ์‹ธ์„œ ๋ฆฌํ„ดํ•˜๋Š” ๋ฐฉ๋ฒ• = (return Observable<T>.create {...})
return Observable.create() { emitter in
    let url = URL(string: url)!
    let task = URLSession.shared.dataTask(with: url) { (data, _, err) in
        guard err == nil else {
            emitter.onError(err!)
            return
        }

        if let dat = data, let json = String(data: dat, encoding: .utf8) {
            emitter.onNext(json)
        }

        emitter.onConpleted()
     }                       
     task.resume()
    return Disposables.create() {
        task.cancel()
    }
}
  • Observable๋กœ ์˜ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์„œ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ• = (Observable.subscribe { ... })
    • Observable์˜ ๋ฆฌํ„ด๊ฐ’์€ Disposable
observable.subscribe { event in
    switch event {
      case .next(let data):
              break
      case .error(let err):
           break
      case .completed:
              break
    }
}
  • Observable์˜ ์ข…๋ฃŒ : .completed, .error, .disposed
    • ์ข…๋ฃŒ ์‹œ์ ์— ํด๋กœ์ €๊ฐ€ ์‚ฌ๋ผ์ง€๋ฏ€๋กœ ์ˆœํ™˜์ฐธ์กฐ๋ฅผ ์˜ˆ๋ฐฉํ•  ์ˆ˜ ์žˆ๋‹ค.

Observable์˜ ์ƒ๋ช…์ฃผ๊ธฐ

  1. create
  2. subscribe (๋™์ž‘ ์‹œ์ž‘)
  3. onNext (๋ฐ์ดํ„ฐ ์ „๋‹ฌ)
  4. onCompleted / onError (์‹คํ–‰ ์ข…๋ฃŒ)
  5. disposed (๋™์ž‘์ด ๋๋‚œ Observable์€ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์—†์Œ - ์ผํšŒ์šฉ์ž„. ๋‹ค์‹œ subscribeํ•ด ๋™์ž‘์„ ์‹œ์ž‘ํ•ด์•ผ ํ•จ)

Observable์„ ์„ ์–ธ = ์‹คํ–‰ ์ด ์•„๋‹ˆ๋‹ค. .subscribe๋ฅผ ํ•  ๋•Œ ์‹คํ–‰์ด ๋œ๋‹ค!

SugarApi - ์—ฐ์‚ฐ์ž

RxSwift์˜ ๊ธฐ๋ณธ๊ธฐ๋Šฅ์„ ์ถ•์•ฝํ•ด ์ฃผ๋Š” API. ๊ท€์ฐฎ์€ ์ž‘์—…๋“ค์„ ์ƒ๋žตํ•ด ์ค€๋‹ค

์—ฐ์‚ฐ์ž : ๋ฐ์ดํ„ฐ๋ฅผ ์ค‘๊ฐ„์— ๋ฐ”๊พธ๋Š” API (map, observeOn..)

์—ฐ์‚ฐ์ž์˜ ์ข…๋ฅ˜

reactivex.io ํŽ˜์ด์ง€์— ๋“ค์–ด๊ฐ€ operator๋กœ ๊ฒ€์ƒ‰ํ•˜๋ฉด ์—ฐ์‚ฐ์ž ์ข…๋ฅ˜๊ฐ€ ๋‚˜์˜จ๋‹ค.

  • https://reactivex.io/documentation/operators.html
  • ๊ตต์€ ๊ธ€์”จ๋กœ ๋œ ์—ฐ์‚ฐ์ž๋Š” : ๋Œ€ํ‘œ ๊ธฐ๋Šฅ์„ ํ•˜๋Š” ์—ฐ์‚ฐ์ž
    • ์ผ๋ฐ˜ ๊ธ€์”จ๋กœ ๋œ ์—ฐ์‚ฐ์ž : ๋Œ€ํ‘œ ์—ฐ์‚ฐ์ž์˜ ํŒŒ์ƒ ์—ฐ์‚ฐ์ž
  • ๋‚ด๊ฐ€ ์›ํ•˜๋Š” ์—ฐ์‚ฐ์ž๋ฅผ ๋นจ๋ฆฌ ์ฐพ๋Š” ๋ฐฉ๋ฒ•?
    • ์ฑ„ํŒ…๋ฐฉ์— ๋ฌผ์–ด๋ณด๊ธฐ(best)ใ…‹ใ…‹
    • ๊ณต์‹๋ฌธ์„œ์—์„œ ์ฐพ์•„๋ณด๊ธฐ

์ž์ฃผ ์“ฐ์ด๋Š” ์—ฐ์‚ฐ์ž

  • Observable.just("๋ฐ์ดํ„ฐ") : ๋ฐ์ดํ„ฐ ํ•˜๋‚˜ ์ „๋‹ฌํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์—ฐ์‚ฐ์ž
  • Observable.from(["hello", "world"]) : ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๋ฅผ ํ•˜๋‚˜์”ฉ ์ „๋‹ฌํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์—ฐ์‚ฐ์ž
  • .subscribe(onNext: { ... } ) : next, completed, error, dispose ์ค‘ ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์‚ฌ์šฉ ํ•œ๋‹ค.
  • .observeOn() : ๋‹ค์Œ ์ค„๋ถ€ํ„ฐ ์‹คํ–‰ํ•  ์Šค๋ ˆ๋“œ๋ฅผ ์ง€์ •
    • downstream(์•„๋ž˜๋กœ) ์˜ํ–ฅ์„ ์ค€๋‹ค
    • DispatchQueue.main.async { ... }๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ํ•œ ์ค„๋กœ ํ•ด๊ฒฐ
    • ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ๋Š” Schedulerํƒ€์ž…์„ ๋„ฃ๋Š”๋‹ค.
    • ์˜ˆ๋ฅผ ๋“ค์–ด MainScheduler.instance, ConcurrentDispatchQueueScheduler(qos: .defalut) ..
  • .subscribeOn() : ๋งจ ์ฒ˜์Œ ์‹œ์ž‘ํ•  ์Šค๋ ˆ๋“œ๋ฅผ ์ง€์ •
    • ์–ด๋””์—์„œ ํ˜ธ์ถœํ•˜๋“  ๋งจ ์ฒ˜์Œ ์Šค๋ ˆ๋“œ๋ฅผ ์ง€์ •ํ•จ
    • upstream(์œ„๋กœ) ์˜ํ–ฅ์„ ์ค€๋‹ค
  • .map { ... },.filter { ... } ...
  • .buffer : ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ์„ ๋•Œ ์ง€์ •๋œ ๊ฐฏ์ˆ˜๋งŒํผ ๋ฌถ์–ด์„œ ๋‚ด๋ ค๋ณด๋‚ด์คŒ
  • .scan : ์ง์ „ ๋ฐ์ดํ„ฐ์™€ ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰
  • .take(ํšŸ์ˆ˜) : ์ง€์ • ํšŸ์ˆ˜๋งŒํผ๋งŒ ์ˆ˜ํ–‰
  • Observable.debug() : Observable๋‚ด์—์„œ ์–ด๋–ค ๋ฐ์ดํ„ฐ๊ฐ€ ์ „๋‹ฌ๋˜๋Š”์ง€๋ฅผ ๋ณด์—ฌ์คŒ

๋งˆ๋ธ” ๋‹ค์ด์–ด๊ทธ๋žจ marble diagram

๋งˆ๋ธ” ๋‹ค์ด์–ด๊ทธ๋žจ๋งŒ ์ดํ•ดํ•˜๋ฉด ์—ฐ์‚ฐ์ž์˜ ๋™์ž‘์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

  • ๋™๊ทธ๋ผ๋ฏธ = ๋ฐ์ดํ„ฐ ๋ฅผ ์˜๋ฏธํ•จ
  • ๋„ค๋ชจ = ์—ฐ์‚ฐ์ž ๋ฅผ ์˜๋ฏธํ•จ
    • ์ƒ‰๊น” : ์“ฐ๋ ˆ๋“œ๋ฅผ ์˜๋ฏธํ•จ
  • ํ™”์‚ดํ‘œ = Observable ์„ ์˜๋ฏธํ•จ
  • ์„ธ๋กœ์ค„ = complete ๋ฅผ ์˜๋ฏธํ•จ

2๊ต์‹œ

Stream์˜ ๋ถ„๋ฆฌ ๋ฐ ๋ณ‘ํ•ฉ

  • ๋ณ‘ํ•ฉ
    • .merge : ์—ฌ๋Ÿฌ Observable์„ ๋ฌถ์–ด ํ•˜๋‚˜์˜ Observable๋กœ ๋‹จ์ˆœํ•˜๊ฒŒ ํ•ฉ์นœ ๊ฒƒ
      • ๋‘ Observable์˜ ๋ฐ์ดํ„ฐํƒ€์ž…์ด ๊ฐ™์•„์•ผ๋งŒ ํ•œ๋‹ค
    • .zip : ๋‘ Observable์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ฉ์ณ ์Œ์œผ๋กœ ๋งŒ๋“ค์–ด ๋ณด๋ƒ„
      • ๋‘ Observable์˜ ๊ธธ์ด๊ฐ€ ๊ฐ™์œผ๋ฉด ์Œ์œผ๋กœ ๋งž๊ฒŒ ๋ณด๋ƒ„
      • ๊ธธ์ด๊ฐ€ ๋‹ค๋ฅด๋ฉด ์งง์€ ์ชฝ์˜ ๊ธธ์ด์— ๋งž์ถค
    • .combineLatest : .zip๊ณผ ๋‹ฌ๋ฆฌ ๋ฐ์ดํ„ฐ ์Œ ๊ฐฏ์ˆ˜๊ฐ€ ์•ˆ๋งž์•„๋„, ์ด์ „ ๋ฐ์ดํ„ฐ ์ค‘ ์ตœ๊ทผ ๊ฒƒ์„ ๊ฐ€์ง€๊ณ  ์Œ์„ ๋งŒ๋“ค์–ด ์ค€๋‹ค

Disposable

  • ์—ฐ์‚ฐ์„ ์ทจ์†Œํ•˜๊ณ  ์‹ถ์„ ๋•Œ, ๊ทธ๋ฆฌ๊ณ  ํ•ด๋‹น ์ŠคํŠธ๋ฆผ์„ ์‚ญ์ œํ•  ๋•Œ ์‚ฌ์šฉ
  • disposeBag : Disposable๋“ค์„ ๋‹ด๋Š” ๊ฐ€๋ฐฉ. ์—ฌ๊ธฐ์— ๋‹ด์•„๋‘๋ฉด ํด๋ž˜์Šค๊ฐ€ ์ง€์›Œ์งˆ ๋•Œ ์ž๋™์œผ๋กœ ์ŠคํŠธ๋ฆผ๋“ค์„ ์ง€์›Œ์ค€๋‹ค.

step2 ์˜ˆ์ œ (RxSwift + MVVM)

  • RxSwift๋ฅผ ์‚ฌ์šฉํ•ด ์–ด๋–ป๊ฒŒ json ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ -> ํ…Œ์ด๋ธ”๋ทฐ์— ๋ฟŒ๋ฆฌ๋Š”์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ์˜ˆ์ œ์ด๋‹ค.
  • ์ฐธ๊ณ ์ž๋ฃŒ๋กœ ๋ณด๊ธฐ

step3 ์˜ˆ์ œ

  • step3 empty๋ฅผ ์—ด์–ด์„œ ๋ทฐ์™€ ์—ฐ๊ฒฐ, RxSwift ์ ์šฉ, RxSwift + MVVM ์ ์šฉ ํ•ด๋ณด๊ธฐ ์—ฐ์Šต์„ ํ•œ๋‹ค.

MVVM

๋ทฐ๋ชจ๋ธ = ๋ทฐ๋ฅผ ์œ„ํ•œ ๋ชจ๋ธ,

  • ๋ทฐ์— ํ•„์š”ํ•œ ๋ชจ๋ธ์ด๋ผ๋Š” ๋œป์ด๋‹ค!
  • ๋ทฐ์— ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค.
  • ์•ฑ์— ํ•„์š”ํ•œ ๋กœ์ง์„ ๋ทฐ๋ชจ๋ธ์ด ๊ฐ–๋Š”๋‹ค.

๋ทฐ์ปจํŠธ๋กค๋Ÿฌ

  • ๋ทฐ ์š”์†Œ๊ฐ€ ์–ด๋–ป๊ฒŒ ๋ณด์—ฌ์งˆ์ง€์— ๋Œ€ํ•œ ๋™์ž‘๋งŒ ์ง€์ •ํ•œ๋‹ค.
  • ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜์ง€๋Š” ์•Š๋Š”๋‹ค.
  • ๋ทฐ๋ชจ๋ธ์„ ๊ฐ–๋Š”๋‹ค (๋ทฐ๋ชจ๋ธ์— ๊ทธ ๋กœ์ง์ด ์žˆ๋‹ค)

RxSwift ์ ์šฉ ์ง€์ 

  • ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด ๋‚ด๊ฐ€ ๋ทฐ ์—…๋ฐ์ดํŠธ ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ๋ทฐ๊ฐ€ ์•Œ์•„์„œ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์œผ๋ฉด ์ข‹๊ฒ ๋‹ค.

Subject

: Observable์ฒ˜๋Ÿผ subscribeํ•ด ๊ฐ’์„ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๊ณ , (์ถ”๊ฐ€์ ์œผ๋กœ) ์™ธ๋ถ€์—์„œ ๊ฐ’์„ ์ปจํŠธ๋กคํ•  ์ˆ˜๋„ ์žˆ๋Š” ๊ฐ์ฒด.

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

  • ์™œ ์“ฐ๋Š”๊ฐ€?
    • create ์‹œ์  ๋ฟ ์•„๋‹ˆ๋ผ, ์ด๋ฏธ ๋งŒ๋“ค์–ด์ง„ subject์— ๋Œ€ํ•ด์„œ๋„ ๊ฐ’ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ
    • Observable๊ณผ์˜ ์ฐจ์ด์  : ์ƒ์„ฑ๋œ ์ดํ›„์—๋„ ์™ธ๋ถ€์—์„œ ๊ฐ’์„ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ๋‹ค. (๋ฐ์ดํ„ฐ ์ฃผ์ž… ๊ฐ€๋Šฅ)
    • PublishSubject์™€ BehaviorSubject๋ฅผ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.

Subject์˜ ์ข…๋ฅ˜

  • PublishSubject()
    • ์ž์‹ ์„ subscribeํ•œ ๊ฐ์ฒด์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ ค์คŒ
    • ์ดํ›„ subscribeํ•œ ๊ฐ์ฒด์—๊ฒŒ๋Š” ๊ทธ ์ดํ›„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ ค์คŒ
  • BehaviorSubject(๊ธฐ๋ณธ๊ฐ’)
    • subscribeํ•˜๋ฉด ๊ธฐ๋ณธ๊ฐ’์„ ๋‚ด๋ ค์คŒ
    • ์ดํ›„ subscribeํ•œ ๊ฐ์ฒด์—๊ฒŒ๋Š” ๊ฐ€์žฅ ์ตœ๊ทผ์˜ ๋ฐ์ดํ„ฐ + ๊ทธ ์ดํ›„์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ ค์คŒ
  • AsyncSubject
    • subscribeํ•œ ๊ฐ์ฒด๋“ค์ด ์žˆ์–ด๋„ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ ค์ฃผ์ง€ ์•Š๋‹ค๊ฐ€,
    • ์ž์‹ ์ด complete๋˜๋Š” ์‹œ์ ์— ๋งจ ๋งˆ์ง€๋ง‰ ๋ฐ์ดํ„ฐ๋งŒ subscribeํ•œ ๊ฐ์ฒด๋“ค์—๊ฒŒ ๋‚ด๋ ค์ฃผ๊ณ  ๋๋‚จ
  • ReplaySubject
    • ์ƒˆ๋กญ๊ฒŒ subscribeํ•œ ๊ฐ์ฒด์—๊ฒŒ ๊ทธ๋™์•ˆ ๋ฐœ์ƒํ•œ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋‚ด๋ ค์คŒ

RxCocoa

: RxSwift์˜ ์š”์†Œ๋“ค์„ UIKit์— ์ ์šฉ์‹œํ‚จ ๊ฒƒ!

UIKit์š”์†Œ.rx๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

  • .bind(to: ๋ฐ”์ธ๋”) : subscribe(onNext: { ... } ) ๋Œ€์‹  ์‚ฌ์šฉ
    • [weak self]๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ์•Œ์•„์„œ ์ˆœํ™˜์ฐธ์กฐ๋ฅผ ์˜ˆ๋ฐฉํ•ด ์ค€๋‹ค.
    • menuObservable.bind(to: totalPrice.rx.text) : dequeue์—ญํ• ์„ ๋Œ€์‹ ํ•จ
      • index = indexPath
      • item = menu ํ•˜๋‚˜ (menuObservable ๋ฐฐ์—ด์˜ ๊ฐ์ฒด ํ•˜๋‚˜)
      • cell = reusableCellํ•˜๋‚˜
    • ์˜ˆ์‹œ : bind(to: tableView.rx.items())

UI์ž‘์—…์˜ ํŠน์ง• - Driver, Relay

  • UI์“ฐ๋ ˆ๋“œ์—์„œ๋งŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•จ = ๋ฉ”์ธ ์“ฐ๋ ˆ๋“œ
    • ํ•ญ์ƒ .observeOn(MainScheduler.instance)๋ฅผ ๋„ฃ์–ด์ค˜์•ผ ํ•จ
  • ์ŠคํŠธ๋ฆผ์ด ๋Š์–ด์ง€๋ฉด ์•ˆ ๋จ - ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค๊ณ  ํ•ด์„œ ํ™”๋ฉด์ด ๋จนํ†ต์ด ๋˜๋ฉด ์•ˆ ๋จ. ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ๊ทธ๊ฑธ ์•Œ๋ ค์ฃผ๊ณ  ๋‹ค์‹œ ์ •์ƒ์ƒํƒœ๋กœ ๋Œ์•„๊ฐ€์•ผ
    • ํ•ญ์ƒ .catchErrorJustReturn()์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•จ
  • ์œ„์˜ ๋‘ ์ฝ”๋“œ๊ฐ€ ํ•ญ์ƒ ๋“ค์–ด๊ฐ€์•ผ ํ•˜๋Š” ๊ฒŒ ๊ท€์ฐฎ์œผ๋‹ˆ๊นŒ, ์ถ•์•ฝํ•˜๋Š” ์ƒˆ API ๋“ฑ์žฅ - Driver
  • Driver : UI์ž‘์—…์„ ์œ„ํ•œ ๋Š์–ด์ง€์ง€ ์•Š๋Š” Observable.
    • asDriver(onErrorJustReturn:์—๋Ÿฌ๋ฐœ์ƒ์‹œ์ฒ˜๋ฆฌํ• ๊ธฐ๋ณธ๊ฐ’)
    • .drive(UI์Šค๋ ˆ๋“œ์—์„œ ํ•  ๋™์ž‘) - ํ•ญ์ƒ UI์Šค๋ ˆ๋“œ์—์„œ ๋Œ์•„๊ฐ
      • .drive(itemCountLabel.rx.text)
    • Driver์— ๋Œ€ํ•ด์„œ๋Š” drive()๋งŒ ํ•˜๋ฉด ๋œ๋‹ค (subscribe ํ•„์š”์—†์Œ)
  • Relay : UI์ž‘์—…์„ ์œ„ํ•ด ๋Š์–ด์ง€์ง€ ์•Š๋Š” subject. (RxRelay)
    • subject์™€ ๋‹ค ๋˜‘๊ฐ™์€๋ฐ ๋Š์–ด์ง€์ง€ ์•Š์Œ. ์˜ค๋กœ์ง€ .next๋งŒ ์กด์žฌํ•จ
    • .onNext() ๋Œ€์‹  .accept()๋งŒ ์‚ฌ์šฉํ•จ

MVC, MVP, MVVM ์ •๋ฆฌ

  • ๊ธฐํš์„œ๊ฐ€ ๋‚˜์™”์„ ๋•Œ, ์„œ๋ฒ„๊ฐ€ ์™„์„ฑ๋˜์ง€ ์•Š์•˜์–ด๋„ iOS๋Š” iOS๋Œ€๋กœ ๊ฐœ๋ฐœ์„ ํ•˜๊ณ  ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  • ์šฐ๋ฆฌ๊ฐ€ ๋จผ์ € ๊ฐœ๋ฐœํ•œ ๋ชจ๋ธ(๋ทฐ ๋ชจ๋ธ)๊ณผ, ์„œ๋ฒ„๊ฐ€ ๋ณด๋‚ด์ค€ ๋ชจ๋ธ (๋„๋ฉ”์ธ ๋ชจ๋ธ)์€ ๋ณดํ†ต ๋‹ค๋ฅด๋‹ค.
    • ๋„๋ฉ”์ธ ๋ชจ๋ธ -> ๋ทฐ ๋ชจ๋ธ Converting ์ž‘์—…์ด ํ•„์š”ํ•˜๋‹ค.

MVC

  • Model
    • UIKit์— ๋…๋ฆฝ์  (ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ)
  • View :
    • UIKit์— ์˜์กด์ 
  • Controller
    • User Input์„ ๋ฐ›๋Š”๋‹ค (IBAction)
    • View Setting์„ ํ•œ๋‹ค
  • ๋‹จ์  : ์ปจํŠธ๋กค๋Ÿฌ ์—ญํ• ์ด ๋„ˆ๋ฌด ํผ & ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒŒ ๋ชจ๋ธ๋ฟ์ž„

MVP

View์™€ Presenter๊ฐ€ 1:1๊ด€๊ณ„

  • Model
  • View (UIViewController)
    • ๋ทฐ + ๋ทฐ์ปจ
    • ์‚ฌ์šฉ์ž input์„ ๋ฐ›์Œ
    • ์–ด๋–ค ์ฒ˜๋ฆฌ๋ฅผ ํ• ์ง€๋Š” presenter์—๊ฒŒ ๋ฌผ์–ด๋ด„ - ํŒ๋‹จ์„ ํ•˜์ง€ ์•Š์Œ
  • Presenter
    • ๋ทฐ์ปจ์˜ ๋กœ์ง์„ presenter๋กœ ๋ถ„๋ฆฌ
    • ๋ทฐ์— ๊ทธ๋ ค์งˆ ์š”์†Œ๋ฅผ ์ง€์ •ํ•จ
  • Model๊ณผ Presenter๋ผ๋Š” ๋‘ ๋ถ€๋ถ„์„ ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋‹จ์  : 1:1๊ด€๊ณ„์ด๊ธฐ ๋•Œ๋ฌธ์— Presenter๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์•„์ง, Presenter๊ฐ€ ๋ชจ๋“  ๊ฑธ ํŒ๋‹จํ•ด์•ผ ํ•จ

MVVM

  • Model
  • View (UIViewController)
    • ๋ทฐ + ๋ทฐ์ปจ
    • ๋ทฐ๊ฐ€ ๋ทฐ๋ชจ๋ธ์„ ๋ฐ”๋ผ๋ณด๊ณ  ์žˆ๋‹ค๊ฐ€, ๋ทฐ๋ชจ๋ธ์˜ ๋ฐ”๋€Œ๋ฉด ์Šค์Šค๋กœ ๊ทธ๋ฆฌ๋„๋ก ๋ฐ”์ธ๋”ฉํ•ด๋‘ 
  • ViewModel
    • Presenter์™€์˜ ์ฐจ์ด์  : ViewModel์ด View์—๊ฒŒ ์ง€์‹œํ•˜์ง€ ์•Š์Œ (ํ™”์‚ดํ‘œ๊ฐ€ ๋‹จ๋ฐฉํ–ฅ์ž„)
    • ๋ทฐ์— ๋ณด์—ฌ์ง€๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ์Œ
  • Model๊ณผ ViewModel ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ

Q. ๋ฐ์ดํ„ฐ๋ฐ”์ธ๋”ฉ ํ•„์ˆ˜์ธ๊ฐ€์š”?

  • ํ•„์ˆ˜๋Š” ์•„๋‹ˆ๋‹ค ๊ทผ๋ฐ ํ•˜๋ฉด ํŽธํ•˜๋‹ค
  • ๋ฐ์ดํ„ฐ๋ฐ”์ธ๋”ฉ ๋Œ€์‹  swiftUI๋ฐฉ์‹์ธ didSet()๋“ฑ์„ ํ™œ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค
  • ๊ทผ๋ฐ ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ง€๋‹ˆ ๊ทธ๋ƒฅ .bind๋ฅผ ์“ฐ๋Š” ๊ฒƒ!