꾸준히 안타치기

MVVM + Rxswift + UIkit을 사용하며 알게된 것 본문

iOS/디자인패턴

MVVM + Rxswift + UIkit을 사용하며 알게된 것

글자줍기 2023. 2. 27. 16:21
반응형

MVVM의 장점 /    ⭐️⭐️⭐️View와  그외 요소들간의 의존성 분리

모듈화가 가능 -> 모듈화가 잘되면 유닛테스트에 용이

뷰와 모델의 간의 종속성이 줄어들어 소스코드를 보기가 편하다.

뷰를 직접 조작하지 않아 영역이 독립적이다.

모듈화가 되어있기 때문에 문제있는 부분만 고치면되서 유지보수가 용이하다.

MVC = 무거워지고 유지보수가 어려워서 개선되서 나온것이 MVVM 선언형 패턴

M 모델 구조체 - 앱의데이터와 비지니스 로직 캡슐화

V는 사용해오던 뷰컨트롤러, UI

ViewModel- 중개자

뷰모델이 model을 갖고 있고, 데이터가 바뀌면 알수있다. 앱의 로직담당

모델이 데이터가 변경되면, 뷰모델에 알려준다.

뷰가 변경되면 -> 액션을 취하면 뷰모델에게 알리고 -> 뷰를 갱신함( 뷰는 뷰모델을 구독하고 있고, 바인딩 되어있음 )

뷰는 모델에 접근하는 대신 뷰모델의 속성에 바인딩한다.

뷰모델은 뷰의 상태를 저장하고, 뷰에서 발생하는 액션에 따라 수행할 앱의 기능을 정의하는 명령을 구현한다.

ex) 모델 데이터 업데이트, 뷰모델 값 변경 


ViewController가 뷰모델을 가지고있고, view와 바인딩

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {
    @IBOutlet var datetimeLabel: UILabel!

    @IBAction func onYesterday() {
        viewModel.moveDay(day: -1)
    }

    @IBAction func onNow() {
        datetimeLabel.text = "Loading.."
        
        viewModel.reload()
    }

    @IBAction func onTomorrow() {
        viewModel.moveDay(day: 1)
    }

    let viewModel = ViewModel()
    let disposeBag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel.dateTimeString
            .bind(to: datetimeLabel.rx.text)
            .disposed(by: disposeBag)

        viewModel.reload()
    }
}

Viewmodel - 로직 기능 구현

import Foundation
import RxRelay

class ViewModel {
    
    let dateTimeString = BehaviorRelay(value: "Loading..")

    let service = Service()

    private func dateToString(date: Date) -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy년 MM월 dd일 HH시 mm분"
        return formatter.string(from: date)
    }
    
    func reload() {
        // Model -> ViewModel
        service.fetchNow { [weak self] model in
            guard let self = self else { return }
            let dateString = self.dateToString(date: model.currentDateTime)
            self.dateTimeString.accept(dateString)
        }
    }

    func moveDay(day: Int) {
        service.moveDay(day: day)
        dateTimeString.accept(dateToString(date: service.currentModel.currentDateTime))
    }
}

model - 앱의 데이터

import Foundation

struct Model {
    var currentDateTime: Date
}

Repository

import Foundation

class Repository {
    func fetchNow(onCompleted: @escaping (UtcTimeModel) -> Void) {
        let url = "http://worldclockapi.com/api/json/utc/now"

        URLSession.shared.dataTask(with: URL(string: url)!) { data, _, _ in
            guard let data = data else { return }
            guard let model = try? JSONDecoder().decode(UtcTimeModel.self, from: data) else { return }
            onCompleted(model)
        }.resume()
    }
}

 Service

import Foundation

class Service {
    let repository = Repository()

    var currentModel = Model(currentDateTime: Date()) // state
    func fetchNow(onCompleted: @escaping (Model) -> Void) {
        
        // Entity -> Model
        repository.fetchNow { [weak self] entity in
            let formatter = DateFormatter()
            formatter.dateFormat = "yyyy-MM-dd'T'HH:mm'Z'"

            guard let now = formatter.date(from: entity.currentDateTime) else { return }

            let model = Model(currentDateTime: now)
            self?.currentModel = model

            onCompleted(model)
        }
    }

    func moveDay(day: Int) {
        guard let movedDay = Calendar.current.date(byAdding: .day,
                                                   value: day,
                                                   to: currentModel.currentDateTime) else {
            return
        }
        currentModel.currentDateTime = movedDay
    }
}

 

https://github.com/iamchiwon/mvvm_final

 

GitHub - iamchiwon/mvvm_final: MVVM 종결 예제

MVVM 종결 예제. Contribute to iamchiwon/mvvm_final development by creating an account on GitHub.

github.com

  1. MVVM 이 DataBinding 자체를 말하는 것이 아니다.
  2. MVVM 에서 VM에 모든 비지니스 로직이 있어야 하는 것이 아니다.
    • 비지니스 로직은 Service 같은 곳에 있어야 하고
    • VM에서는 화면용 데이터를 갖고 있는것,
    • Model 을 View용 Model 로 변경하는 정도의 로직 만 있으면 된다.
  3. MVVM의 데이터 의존관계를 일관성있게 유지하기 위해서 VM은 V를 알면 안된다.
  4. MVVM 의 DataBinding를 위해서 반드시 RxSwift 같은 것을 사용해야만 하는 것은 아니다.

🧑‍💻 생각정리 

MVVM을 에서 뷰를 바인딩할때 꼭 Rxswift를 사용할 필요는없다.

MVVM구조를 알고, Rxswift를 별도로 공부한 다음에 적용하는 것이 좋을듯,  Rxswift양이 많음..

UIkit에서 MVVM을 사용하기는 조금 빡쎄다.

SwiftUI 를 하다보면 구성 자체가 MVVM으로 되어 있어 어쩔 수 없이 MVVM을 사용하게 된다.

(SwiftUI는 C가 없고 Model과 View만 있는 형태. swiftUI에서 뷰는 모델과 직접 바인딩 할 수 있다.)

Rx는 테이블뷰를 델리게이트패턴을 사용하지 않고, 옵져버블과 바인딩해서 사용한다.

 

참고링크

https://tech.olx.com/clean-architecture-and-mvvm-on-ios-c9d167d9f5b3

 

Clean Architecture and MVVM on iOS

When we develop software it is important to not only use design patterns, but also architectural patterns. There are many different…

tech.olx.com

 

반응형

'iOS > 디자인패턴' 카테고리의 다른 글

MVVM의 개념과 나오게된 배경  (0) 2022.10.05
싱글턴 패턴  (0) 2022.05.25
MVC 패턴(Model - View -Controller)  (0) 2021.12.16
프로그래밍 디자인패턴  (0) 2021.12.16
Comments