[swift] RxDataSources와 VIPER 아키텍처의 결합 방법

이번 포스트에서는 RxSwift에서 자주 사용되는 RxDataSources와 VIPER 아키텍처를 결합하는 방법에 대해 알아보겠습니다.

RxDataSources 소개

RxDataSources는 RxSwift와 함께 사용하는 TableView와 CollectionView 데이터 소스 라이브러리입니다. 이 라이브러리는 TableView와 CollectionView의 데이터 연동을 쉽고 간편하게 처리할 수 있게 해줍니다. RxDataSources는 테이블의 섹션을 정의하고 각 섹션에 해당하는 아이템들을 관리하는 기능을 제공합니다.

VIPER 아키텍처와의 결합

VIPER는 View, Interactor, Presenter, Entity, Router로 구성된 소프트웨어 아키텍처 패턴입니다. 각각의 역할이 분리되어 있어 유지보수성과 테스트 용이성을 높일 수 있습니다. RxDataSources를 사용하면 VIPER 아키텍처에서 View와 Presenter 간의 데이터 연동을 간편하게 처리할 수 있습니다.

View

View는 화면에 대한 UI를 담당하고 있습니다. TableView나 CollectionView와 같은 UI 컴포넌트를 사용하여 화면을 구성하고, Presenter와 데이터를 주고받습니다.

class MyViewController: UIViewController {

    var presenter: MyPresenter!
    
    // TableView 초기화
    private let tableView = UITableView()
    private let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Item>>(
        configureCell: { _, tableView, indexPath, item in
            let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
            cell.textLabel?.text = item.title
            return cell
        },
        titleForHeaderInSection: { dataSource, sectionIndex in
            return dataSource[sectionIndex].model
        }
    )
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // TableView 설정
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        tableView.dataSource = nil
        tableView.delegate = nil
        
        // Presenter에 요청하여 데이터 로드
        self.presenter.loadData()
    }
    
    // 데이터 연동
    func bindData(_ items: Observable<[SectionModel<String, Item>]>) {
        items
            .bind(to: tableView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)
    }
    
}

Presenter

Presenter는 View와 모델간의 중개자 역할을 수행합니다. View에서 요청한 데이터를 처리하고, 적절한 데이터를 View에 전달합니다. RxSwift를 사용하여 비동기 작업을 처리하고, 데이터를 가공한 후 View로 전달합니다.

class MyPresenter {
    
    weak var view: MyViewController?
    var interactor: MyInteractor!
    
    func loadData() {
        interactor.loadItems()
            .observeOn(MainScheduler.instance)
            .subscribe(onNext: { [weak self] items in
                let sections = [SectionModel(model: "Section 1", items: items)]
                self?.view?.bindData(Observable.just(sections))
            })
            .disposed(by: disposeBag)
    }
    
}

Interactor

Interactor는 비즈니스 로직을 담당합니다. 데이터의 로드 및 가공을 수행하고, Presenter에게 결과를 전달합니다.

class MyInteractor {
    
    func loadItems() -> Observable<[Item]> {
        // 비동기 작업을 수행하고 결과를 Observable로 반환
    }
    
}

위의 예시에서는 Presenter에서 Interactor의 loadItems() 메서드를 호출하여 데이터를 로드하고 View로 전달합니다. 이때 비동기 작업은 RxSwift를 사용하여 처리하며, 데이터 로드 완료 후 결과를 바로 View에 바인딩합니다.

결론

RxDataSources와 VIPER 아키텍처를 결합하면 View와 Presenter 사이의 데이터 연동을 간편하게 처리할 수 있습니다. RxDataSources를 사용하면 TableView와 CollectionView의 데이터 소스를 쉽고 간편하게 구현할 수 있으며, VIPER 아키텍처는 코드의 유지보수성과 테스트 용이성을 높일 수 있습니다.

참고 자료