HJLEE-22 / WeatherAppNational

WeatherKit을 이용한 어제 날씨 비교 앱
3 stars 1 forks source link

[Issue04] MVVM 리팩토링 #4

Open HJLEE-22 opened 2 years ago

HJLEE-22 commented 2 years ago

해결하고자 하는 주제에 대해 설명 해 주세요.

위 주제를 해결할 수 있는 방안으로는 어떤 것이 있나요?

어떤 이슈와 연관성이 있나요?

다음 작업들이 진행되어야 합니다.

참고 사항

참고 할만한 문서나, 서비스의 링크를 적어 주세요.

HJLEE-22 commented 2 years ago

mvvm 리팩토링 정리

  1. 시작 : Observer / Subscriber 프로토콜 생성

    • 구현의 핵심. VC가 옵저버, VM가 섭스크라이버가 되어, 서로의 객체 생성 없이 Model 데이터를 주고받는다.
      • 객체 생성 없는 이유 : 델리게이트 패턴과 비슷한 프로토콜로 model을 notify(섭스크라이버)하고 update(옵저버)를 행하기에 가능.
      • 객체 생성 없는 장점 : 서로가 연결되어있지 않으니 유지보수 할 때 매우 용이.
      • 옵저버/섭스크라이버 방식 이유 : UINotificationCenter 방식을 변용. 1:N으로 연결과 비동기 처리에 유리.
    • Observer / Subscriber 코드

      • Observer (VC)
      protocol Observer {
          func update<T>(updatedValue: T)
      }
      • Subscriber (VM)
      protocol Subscriber {
          var observer: (any Observer)? { get set }
          mutating func unSubscribe(observer: (any Observer)?)
          mutating func subscribe(observer: (any Observer)?)
          func notify<T>(updatedValue: T)
      }
  2. Model / ViewModel / View(ViewController) 역할

    • View

      • View에는 View를 구성하는 UI와, 외부(VC)에서 던져주는 Model 객체만 존재
      // View는 VC에서 Model을 받아 View를 configure한다.
          // model을 받을때마다 데이터가 변동되야 하니까 didset.
          var weatherModel: WeatherModel? {
              didSet {
                  if let weatherModel = weatherModel {
                      self.configureUI(weatherModel)
                  }
              }
          }
    • ViewModel

      • Subscriber 프로토콜 채택. 프로토콜의 메서드들에 기본값 제공.

        // 서브스크라이버 프로토콜 초기화. 기본값 넣어주기.
        
        extension WeatherViewModel: Subscriber {
            func unSubscribe(observer: (Observer)?) {
                self.observer = nil
            }
        
            func subscribe(observer: (any Observer)?) {
                self.observer = observer
            }
        
            func notify<T>(updatedValue: T) {
                observer?.update(updatedValue: updatedValue)
            }
        }
      • VC와 연결할 observer 객체 생성.

        // VC를 받을 옵저버 객체 만들어놓기 (일종의 델리게이트 프로퍼티)
            // 질문 : 구지 internal로 선언하는 이유는 무엇인가요?
            // 질문 : any 가 무엇인가요...?
            internal var observer: (any Observer)?
      • ModelDataManager로부터 Model을 받아와 model객체를 초기화.

      • 이렇게 초기화한 model 값을 subscriber 프로토콜의 notify를 통해 전달

        private var todayWeatherModel: WeatherModel = WeatherModel() {
                didSet {
                    notify(updatedValue: [Day.today: todayWeatherModel])
                }
            }
        // 오늘 날씨
                DispatchQueue.global().async { [weak self] in
                    guard let selfRef = self else { return }
                    WeatherService.shared.fetchWeatherData(dayType: Day.today,
                                                           date: DateCalculate.yesterdayDateString,
                                                           time: "2300",
                                                           nx: selfRef.nx,
                                                           ny: selfRef.ny) { result in
                        switch result {
                        case .success(let weatherModel):
                            selfRef.todayWeatherModel = weatherModel
        
                        case .failure(let error):
                            print("오늘 날씨 불러오기 실패", error.localizedDescription)
                        }
                    }
                }
    • ViewController

      • VM에서 받아온 Model을 View에 던져주는 역할

      • 옵저버 프로토콜을 채택하고, update 함수에 전달하기 원하는 데이터 타입 구성.

      • (각) View에 데이터를 전달한다.

        • 유념 : update 함수는 subscriber 프로토콜에 notify 메서드로 연결되어 있다. 이후 직접 호출되지 않음. (update 메서드에 입력받는 파라미터도 notify 메서드의 파라미터와 연결되어있음)
        // 이렇게 VC가 옵저버가 됬다면, 유일하게 할 일은 뷰에 데이터 모델을 전달하는 것...
        // 즉, View와 model의 연결은 VC에서...
        // 그런데 Model은 VM에서 만들었는데 왜 이렇게 하느냐?
        // VC/View에서 Model객체를 초기화(생성)하지 않기 위해서!! 이렇게 구지구지 돌고돌아 가는것!
        
        // 그러고보면 VC는 Model이랑 View와 VM 모두를 연결하고 있네...?
        // VC의 관계 정리 :
        // 1. VC는 스스로 옵저버가 되어, VM을 섭스크라이브 한다. 여기서 VM이 초기화하는 Model을 받아온다.
        // 2. VC는 Model을 초기화하지 않고, VM을 통해 받아와 View에 전달한다.
        
        extension WeatherViewController: Observer {
            func update<T>(updatedValue: T) {
                guard let value = updatedValue as? [Day: WeatherModel] else { return }
                DispatchQueue.main.async { [weak self] in
                    switch value.first?.key {
                    case .today:
                        self?.mainView.todayWeatherView.weatherModel = value[.today]
                    case .tomorrow:
                        self?.mainView.tomorrowdayWeatherView.weatherModel = value[.tomorrow]
                    case .yesterday:
                        self?.mainView.yesterdayWeatherView.weatherModel = value[.yesterday]
                    case .none:
                        break
                    }
                } 
            }
        }
      • VM에게 자신이(해당 VC가) 옵저버임을 알려야 함

        • VM 프로퍼티 감시자로 만들어 subscribe할 옵저버 대상을 자신으로 놓기.
        // VC는 VM에게 알리기만 한다! VM이 알아서 다 해먹도록!
            // 그래서 VC에선 VM의 섭스크라이브만 해놓고, 옵저버를 VC 자신으로 등록.........
            var viewModel: WeatherViewModel! {
                didSet {
                    viewModel.subscribe(observer: self)
                }
            }
        • 이렇게 한 subscribe는 차후 해제해야 함