[포켓몬 도감 앱 만들기] 리팩토링 5

황석범·2025년 1월 19일
0

내일배움캠프_iOS_5기

목록 보기
66/76

수정하고 싶은 부분

현재 뷰컨트롤러가 뷰모델에 직접 데이터를 요청하는 구조 -> viewDidLoad나 viewWillApear 이벤트를 뷰모델에 전달하고 뷰모델이 상태를 변경하면서 데이터를 emit하도록 구조를 변경

현재 구조

MainViewController

 override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.fetchPokemonData()
        bind()
    }

개선 방향

1. 뷰모델에 viewDidLoad 이벤트를 처리할 Subject 추가

  • PublishSubject 또는 BehaviorSubject를 사용하여 이벤트를 전달합니다.

2. 뷰모델에서 이벤트를 구독하고 처리:

  • viewDidLoadSubject를 구독하여 필요한 상태 변화를 처리합니다.

3. 뷰컨트롤러 수정:

  • viewModel.fetchPokemonData() 대신 viewDidLoadSubject를 통해 이벤트를 전달합니다.

MainViewModel

 final class MainViewModel { 
  
 	let viewDidLoadSubject = PublishSubject<Void>()
  
  	private func bindViewDidLoad() {
        viewDidLoadSubject
            .subscribe(onNext: { [weak self] in
                self?.fetchPokemonData()
            })
            .disposed(by: disposeBag)
    }
  
  ...
  
  
}

MainViewController

  override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.viewDidLoadSubject.onNext(())
        bind()
    }

개선 사항

1. 이벤트 기반 구조:

  • 뷰모델에서 직접 데이터를 요청하지 않고, 이벤트(viewDidLoadSubject)를 통해 로직을 시작합니다.

2. 분리된 책임:

  • viewModel은 viewDidLoad를 수신받아 자체적으로 데이터를 요청하고 상태를 업데이트합니다.
  • 뷰컨트롤러는 UI 이벤트만 전달하고 상태를 구독합니다.

3. 확장 가능성:

  • 이 구조는 다른 이벤트(viewWillAppear, refreshTriggered) 등에도 쉽게 대응할 수 있습니다.

추가 개선

viewDidLoadSubject를 제거하고, 이를 rx.viewDidLoad로 대체해보자

UIViewController+Reactive

import UIKit
import RxSwift
import RxCocoa

extension Reactive where Base: UIViewController {
    /// Observable for `viewDidLoad` lifecycle event.
    var viewDidLoad: Observable<Void> {
        return methodInvoked(#selector(UIViewController.viewDidLoad))
            .map { _ in }
    }
}

MainViewController


개선 사항

  1. rx.viewDidLoad 도입
  • UIViewController+Rx 확장을 통해 viewDidLoad 이벤트를 Rx 스트림으로 노출.
  • 뷰모델에서 viewDidLoadSubject를 제거하고, 뷰컨트롤러가 rx.viewDidLoad를 직접 사용하여 데이터를 요청.
  1. 책임 분리
  • 뷰컨트롤러는 viewDidLoad 이벤트를 구독하여 뷰모델의 메서드를 호출.
    • 뷰모델은 이벤트 전달 없이 상태 관리와 데이터 로드만 수행.
  1. 확장성
  • 동일한 방식으로 다른 생명주기 메서드(viewWillAppear, viewDidAppear 등)를 Rx로 변환 가능.
  • 코드가 간결해지고 유지보수가 쉬워짐.

여기도 바꿔보자

개선 방향

1. 뷰모델에서 데이터 로드 트리거를 관리:

  • loadMoreTrigger는 PublishSubject로 데이터 로드 요청을 관리.
  • 내부적으로 loadMoreTrigger를 구독하여 실제 데이터를 로드.

2. 뷰컨트롤러에서 트리거 이벤트를 전달:

  • rx.viewDidLoad 및 rx.contentOffset에서 각각 loadMoreTrigger에 이벤트 전달.
  • 뷰컨트롤러는 데이터를 직접 요청하지 않고, 트리거만 전달.

3. Rx 기반 데이터 흐름:

  • 뷰컨트롤러는 rx.viewDidLoad와 스크롤 이벤트만 트리거하고, 뷰모델은 이를 처리해 데이터를 방출.
  • 이벤트 기반으로 데이터 로드와 상태 관리.

MainViewController

mainView.collectionView.rx.contentOffset
            .map { [weak self] offset -> Bool in
                guard let self = self else { return false }
                let contentHeight = self.mainView.collectionView.contentSize.height
                let height = self.mainView.collectionView.frame.size.height
                return offset.y + height > contentHeight - 100
            }
            .distinctUntilChanged() // 중복 호출 방지
            .filter { $0 } // true인 경우만 통과
            .map { _ in () } // Void로 변환
            .bind(to: viewModel.loadMoreTrigger)
            .disposed(by: disposeBag)

개선 효과

  1. 뷰컨트롤러는 이벤트를 전달하는 역할만 수행.
  2. 뷰모델은 상태와 로직을 전담.
  3. 코드가 더욱 명확해지고 유지보수가 용이해집니다.
profile
iOS 공부중...

0개의 댓글

관련 채용 정보