[iOS] MVVM → ReactorKit 리팩토링

김상우·2023년 6월 6일
1
post-custom-banner

ReactorKit : https://github.com/ReactorKit/ReactorKit/tree/master


What is ReactorKit

ReactorKit 은 단방향 흐름을 가진, 반응형 앱을 위한 프레임워크. 카카오, 토스를 포함한 많은 기업들이 사용하고 있다.

내부적으로 RxSwift를 활용하고 있으며, Reactor 라는 객체가 MVVM 의 ViewModel 과 비슷한 역할을 수행한다.

  • View → Action → Reactor
  • Reactor → State → View

위와 같은 단방향성 흐름을 추구한다. 예를들어, 날씨 앱이라고 했을 때, 유저가 날씨를 요청하는 버튼을 클릭하고 서버에서 날씨를 받아오는 경우.

  1. 유저가 날씨화면에서 = View
  2. 버튼을 클릭한다 = Action
  3. Reactor 의 내부 로직을 수행한뒤 State 에 받아온 날씨를 업데이트한다.
  4. State 에 View 가 반응해서 유저에게 날씨를 보여준다.

ReactorKit 장점

MVVM 패턴은 개발자들이 애용하는 디자인 패턴이지만, 회사에따라, 구현하는 개발자에 따라 템플릿이 조금씩 다르다. ViewModel 에 Input Output 을 선언하는 경우도 있고, 그렇지 않은 경우도 있다.

ReactorKit 은 Reactor 객체에 enum Action, Mutation, State 를 규격화 해놓았기 때문에 조금 더 개인의 차이를 줄이고 일관된 코드를 작성할 수 있다.


MVVM → Reactor 리팩토링

Soma Weather App 에서, 도시의 이름을 영어로 검색하면, 그 도시의 날씨를 보여주는 페이지를 ViewModel 에서 Reactor 로 리팩토링 해봤다.

https://github.com/socar-abel/Soma-WeatherApp

SearchViewModel

//
//  SearchViewModel.swift
//  Search
//
//  Created by 김상우 on 2023/04/03.
//  Copyright © 2023 soma. All rights reserved.
//

import Domain
import CommonUI
import RxSwift
import RxCocoa

protocol SearhViewModelProtocol {
    func requestCityWeather(city: String?)
}

public class SearchViewModel: SearhViewModelProtocol {
    let disposeBag = DisposeBag()
    let weatherUseCase: WeatherUseCase
    public weak var searchCoordinator: SearchCoordinator?
    
    /// 도시 날씨 옵저빙
    let weatherRelay = PublishRelay<WeatherVO>()
    
    /// 에러 옵저빙
    let errorRelay = PublishRelay<Bool>()
    
    public init(weatherUseCase: WeatherUseCase) {
        self.weatherUseCase = weatherUseCase
    }
    
    func requestCityWeather(city: String?) {
        guard let city = city else { return }
        weatherUseCase.getCityWeather(city: city)
            .subscribe(onSuccess: { [weak self] response in
                guard let response = response else { return }
                self?.weatherRelay.accept(response)
            }).disposed(by: disposeBag)
    }
}

SearchViewReactor

//
//  SearchViewReactor.swift
//  Search
//
//  Created by abel on 2023/05/29.
//  Copyright © 2023 soma. All rights reserved.
//

import ReactorKit
import RxSwift
import Domain

public final class SearchViewReactor: Reactor {
    private let disposeBag = DisposeBag()
    private let weatherUseCase: WeatherUseCase
    public var initialState: State
    
    public enum Action {
        /// 도시의 날씨 검색
        case searchCityWeather(String)
    }
    
    public enum Mutation {
        /// 도시 날씨 정보 세팅
        case setCityWeather(WeatherVO)
        /// 로딩 중 세팅
        case setLoading(Bool)
    }
    
    public struct State {
        /// 검색한 도시의 날씨
        var weather: WeatherVO?
        /// 로딩 중
        var isLoading: Bool
    }
    
    public init(weatherUseCase: WeatherUseCase) {
        self.weatherUseCase = weatherUseCase
        initialState = State(
            weather: nil,
            isLoading: false
        )
    }
    
    // Action -> Mutation
    public func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case let .searchCityWeather(cityName):
            return performSearchCityWeather(cityName: cityName)
        }
    }
    
    // Mutation -> State
    public func reduce(state: State, mutation: Mutation) -> State {
        var state = state
        switch mutation {
        case let .setCityWeather(weather):
            state.weather = weather
            
        case let .setLoading(isLoading):
            state.isLoading = isLoading
        }
        return state
    }
    
    /// 도시의 날씨 가져오기
    func performSearchCityWeather(cityName: String) -> Observable<Mutation> {
        if currentState.isLoading { return .empty() }
        return .concat(
            .just(.setLoading(true)),
            weatherUseCase.getCityWeather(city: cityName)
                .asObservable()
                .catchAndReturn(nil)
                .compactMap { $0 }
                .map { .setCityWeather($0) },
            .just(.setLoading(false))
        )
    }
}
profile
안녕하세요, iOS 와 알고리즘에 대한 글을 씁니다.
post-custom-banner

0개의 댓글