관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] Factory 패턴으로 비즈니스 모델을 UI모델로 변경하는 좋은 방법 (UI 모델, 비즈니스 모델, 도메인 모델, 데이터 모델) 본문

iOS 응용 (swift)

[iOS - swift] Factory 패턴으로 비즈니스 모델을 UI모델로 변경하는 좋은 방법 (UI 모델, 비즈니스 모델, 도메인 모델, 데이터 모델)

jake-kim 2024. 3. 13. 01:22

비즈니스 모델을 UI 모델로 변경하기

  • 만약 MVVM을 사용한다면, API호출을 통해 얻은 데이터들을 ViewModel에서 받아오고 UI 쪽으로 다시 전달해주어야 하는데 정석으로는 UI / Domain / API 세 계층 모두 모델을 다르게하여 의존성을 줄이는 것이 이론적으로 좋은데 이 때 모델을 다르게하면 model간 변환하는 로직이 필요
  • Domain에서 UI모델로 데이터를 변환할 때, Factory 패턴을 사용하면 로직 분리가 용이
  • Factory라는 의미는 단순히 UI모델로 변경하는 작업 뿐만이 아닌, UI모델에서 필요한 값들도 같이 넣어주도록 구현
    • 만약 api로부터 (age: 20)와 같은 값을 가져오는 경우 UI에서는 20살이라고 표현해야할 때 Factory에서 "20"과 "살"을 결합하여 생성하는 것

ex) API와 Domain에서 모델이 같다고하고 UI 모델만 다른 경우, UI모델로 변경하는 코드

  • bad case: ViewModel에서 직접 변경하는 경우
    • ViewModel에서 requestAPI를 하여 API에서 얻어온 [Int]타입들을 UI에서 필요한 Model 타입으로 변경 후 UI쪽에 updateUI를 호출
    • 사용된 MVVM 모델 구조 설명은 이전 포스팅 글 참고
final class ViewModel: ViewModelable {
   ...
   
    // MARK: Output
    var output: RxSwift.Observable<State> {
        outputSubject
    }
    private var outputSubject = PublishSubject<State>()
    
    // MARK: Input
    func input(_ action: Action) {
        switch action {
        case .viewDidLoad:
            requestAPI()
                .subscribe { [weak self] data in
                    self?.dataSource = data
                        .map { Model(n: String($0) + "번째") }
                    self?.outputSubject.onNext(.updateUI)
                }
                .disposed(by: disposeBag)
        }
    }
}

func requestAPI() -> Single<[Int]> {
    .just((1...10).map { $0 })
}
  • good case: Factory 패턴을 사용하는 경우

(factory 정의)

enum SomeFactory {
    static func makeFirstUIModel(apiResponse: [Int]) -> [Model] {
        apiResponse
            .map {
                .init(n: String($0) + "번째")
            }
    }
}
  • viewModel에서 사용
    • SomeFactory는 protocol로 표현하고 ViewModel에서 factory도 주입받을 수 있도록 구현하면 더욱 베스트 (testable)
            
            
            requestAPI()
                .subscribe { [weak self] data in
//                    self?.dataSource = data
//                        .map { Model(n: String($0) + "번째") }
                    self?.dataSource = SomeFactory.makeFirstUIModel(apiResponse: data)
                    self?.outputSubject.onNext(.updateUI)
                }
                .disposed(by: disposeBag)

정리

  • Factory로 구현하면 데이터 모델을 convert하는 긴 코드들이 factory에 모여있으므로 응집도가 크고 코드 관리가 용이
  • 위에서 알아본 UI에 필요한 "번째" 문구같은 것도 Factory에서 담당하게 되면 ViewController의 역할은 단순히 데이터를 표현하는 역할로 담당할 수 있게하여 뷰의 몸집이 커지지 않고 역할을 분명하게 가능

* 전체 코드

- https://github.com/JK0369/ExFactoryUIModel

- https://github.com/JK0369/ExFactoryUIModel

Comments