ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • MVVM
    Architecture + Design Pattern 2022. 9. 26. 10:47

    Server로 부터 날짜를 가져오고, 날짜를 화면에 띄워주는 서비스를 생각해보자.

     

     

    먼저 Repository 에서 Server 날짜를 가져온다. 

    가져오는 Server 의 데이터 (Entity)는 우리가 원하는 데이터 자체가 아니다.

    즉, 가공이 필요하다. 

     

    따라서 그 다음으로 Service 에서 Repository 에 있는

    Entity 를 가져와 이를 우리가 사용할 데이터 형태(Model)로 가공한다.  

     

    그 후에는 가공된 데이터를 화면에 보여주어야 하는데,

     Service 에는 'Date' Type 이 사용되지만 화면에서는 String Type이 필요하므로 데이터 형태의 변환이 또다시 필요하다.

     

    이때, 그 가공을 맡는 곳을 ViewModel 이라고 한다.

     

    그 후 View 에서 ViewModel 의 데이터를 가져와 화면에 보여주면 일련의 프로세스가 완료된다. 

     

    여기서, Model 은  Entity, Model, ViewModel 로 총 3개이다.

    각 Model 의 사용 목적은 같으나, 사용처(Repo, Service, VM) 는 다르다. 

     

     

    이 중 비즈니스 로직은 Service 에서 처리하며, 가장 중요한 부분이다.

     

    데이터는 위에서 아래로 이동하며 가공되게 되는 반면, 

    의존성은 아래에서 위 방향을 향한다.

     

    의존성에 대해 쉽게 말하면, View 는 ViewModel 데이터를 사용하고, ViewModel 은 View 에 대해서 알 수 없는 관계이다. 즉 말그대로 View 가 ViewModel 에 '의존' 한다.

     

    이때, ViewModel 의 값을 View 에 실시간으로 보여주기 위해서는 ViewModel 의 값을 모니터링 해야하는데, 이때 여러 스타일의 Data Binding 이 사용될 수 있다.

    asyncronous

    asynchronous

    그 중 가장 많이 쓰이는 Binding 방식이 RxSwift 를 이용한 방식이다. RxSwift 가 Data Binding 에 유리한 몇가지 특징 (asynchronous process in a stream way 등) 을 가지고 있으나,  반드시 Binding 에 이것만 쓰여야 하는 것은 아니다. 

     

     

    위와 같은 흐름이 자연스러운 흐름이라면 MVP 에서는 VM 이 View 에게 명령하여 방향이 반대로 흐르게 되고, 
    MVC 에서는 Service 가 View, Repository 에게 모두 명령해서 또한 데이터 흐름이 일관되지 못하게 된다. 
    따라서 두 (MVP, MVC) 는 데이터 Flow 가 부자연스럽다고 할 수 있다. 

     

     

     

     

    예시 코드 

     

     

    Entity

    import Foundation
    
    struct UtcTimeModel: Codable {
        let id: String
        let currentDateTime: String
        let utcOffset: String
        let isDayLightSavingsTime: Bool
        let dayOfTheWeek: String
        let timeZoneName: String
        let currentFileTime: Int
        let ordinalDate: String
        let serviceResponse: String?
    
        enum CodingKeys: String, CodingKey {
            case id = "$id"
            case currentDateTime
            case utcOffset
            case isDayLightSavingsTime
            case dayOfTheWeek
            case timeZoneName
            case currentFileTime
            case ordinalDate
            case serviceResponse
        }
    }

     

     

    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
        }
    }

     

    Model

    import Foundation
    
    struct Model {
        var currentDateTime: Date
    }

     

     

     

     

    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))
        }
    }

     

     

     

    View ( ViewController)

    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()
        }
    }

     

     

    출처: https://www.youtube.com/watch?v=M58LqynqQHc

    'Architecture + Design Pattern' 카테고리의 다른 글

    ReactorKit  (0) 2022.10.12
    Clean Architecture + MVVM  (0) 2022.10.03
    Clean Architecture  (0) 2022.09.25
    Delegate & Protocol  (0) 2022.03.28
    Architecture Patterns ( MVC, MVP,MVVM ) ( with iOS, Swift)  (0) 2021.10.21
Designed by Tistory.