RxSwift에 대해서 알아보자

RxSwift에 대해서 알아보자

RxSwift는 iOS와 macOS 개발에서 사용되는 반응형 프로그래밍 라이브러리이다. 이 라이브러리는 Rx(Reactive Extensions) 패턴을 기반으로 하며, 비동기 데이터 스트림을 쉽게 처리하고 관리할 수 있게 해준다.

 

RxSwift가 나오게 된 이유

  • 비동기 작업의 간소화 : 비동기 작업(네트워크 , 사용자 입력 등)을 처리할때 콜백이나 델리게이트 패턴을 사용하는 것은 코드 복잡도를 증가시킨다. RxSwift는 이러한 비동기 작업을 Observable을 통해 간결하게 표현할 수 있다.
  • 코드의 가독성 및 유지보수성 향상 : RxSwift를 사용하면 데이터 흐름을 명확하게 정의할 수 있어 코드의 가독성과 유지보수성이 향상된다.
  • 함수형 프로그래밍의 장점 : RxSwift는 함수형 프로그래밍 패러다임을 따르기 때문에 부수 효과를 줄이고, 선언적 프로그래밍 스타일을 촉진한다.

RxSwift 기본 개념

  • Observable : 데이터를 시간에 따라 비동기적으로 생성하는 스트림, Observable은 구독(subscribe)함으로써 데이터나 이벤트를 전달받을 수 있다.
  • Observer : Observable을 구독하여 데이터를 수신하는 객체
  • Operators : Observable에서 생성된 데이터를 변환하거나 필터링 하는데 사용되는 함수
  • Schedulers : 코드가 실행될 스레드를 지정하는데 사용된다. 주로 메인 스레드에서 UI업데이트를 하거나 백그라운드 스레드에서 네트워크 작업을 처리할 때 사용된다.

네트워킹 작업 시 RxSwift 예시

pod 'RxSwift'
pod 'RxCocoa'
pod 'Alamofire'

이 예시에서는 이렇게 세가지 라이브러리를 사용할 예정이다. 나는 SPM을 사용하고 있어서 이 방법으로 추가하진 않을 예정이지만 정상적으로 작동하게끔 넣어주기만 하면 된다.

 

GitHub에서 사용자 목록을 가져온다고 가정하고 예시를 만들어보자

struct User: Decodable {
    let id: Int
    let login: String
    let avatar_url: String
}

먼저 가져올 데이터 모델링을 정의한다.

import Alamofire
import RxSwift

class NetworkService {
    func fetchUsers() -> Observable<[User]> {
        return Observable.create { observer in
            let request = AF.request("<https://api.github.com/users>")
                .responseDecodable(of: [User].self) { response in
                    switch response.result {
                    case .success(let users):
                        observer.onNext(users)
                        observer.onCompleted()
                    case .failure(let error):
                        observer.onError(error)
                    }
                }
            
            return Disposables.create {
                request.cancel()
            }
        }
    }
}

NetworkService 클래스를 만들어 알라모파이어와 RxSwift를 활용해 API 데이터를 가져오는 작업을 실행한다.

이 과정을 통해 깃허브에서 데이터 모델에 있는 데이터를 가져온다고 보면된다.

 

다음으로 네트워크 요청 결과를 처리하고 이를 UI에 바인딩 할 ViewModel을 정의한다.

import RxSwift
import RxCocoa

class UsersViewModel {
    let users: BehaviorRelay<[User]> = BehaviorRelay(value: [])
    private let networkService = NetworkService()
    private let disposeBag = DisposeBag()
    
    func fetchUsers() {
        networkService.fetchUsers()
            .observe(on: MainScheduler.instance)
            .subscribe(onNext: { [weak self] users in
                self?.users.accept(users)
            }, onError: { error in
                print("Error fetching users: \\(error)")
            })
            .disposed(by: disposeBag)
    }
}

이렇게 구현한 VM을 하나하나 살펴보자

func fetchUsers() {
    networkService.fetchUsers()
        .observe(on: MainScheduler.instance)
        .subscribe(onNext: { [weak self] users in
            self?.users.accept(users)
        }, onError: { error in
            print("Error fetching users: \\(error)")
        })
        .disposed(by: disposeBag)
}

networkService.fetchUsers() : 네트워크 요청을 Observable로 감싸서 반환한다.

.observe(on: MainScheduler.instance) : 결과를 메인 스레드에서 처리하도록 설정한다.

.subscribe(onNext:) : 네트워크 요청 결과를 구독하고, 성공 시 ‘users’ 속성을 업데이트 한다.

.disposed(by: disposeBag) : 메모리 관리를 위해 DisposeBag에 추가한다.

 

이렇게 VM을 구현했다면 VC에 바인딩 하면 된다.

import UIKit
import RxSwift
import RxCocoa

class UsersViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    
    private let viewModel = UsersViewModel()
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupTableView()
        bindViewModel()
        
        viewModel.fetchUsers()
    }
    
    private func setupTableView() {
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "UserCell")
    }
    
    private func bindViewModel() {
        viewModel.users
            .bind(to: tableView.rx.items(cellIdentifier: "UserCell")) { index, user, cell in
                cell.textLabel?.text = user.login
            }
            .disposed(by: disposeBag)
    }
}

VM 인스턴스를 통해서 UsersVM을 가져오고 VM에서 구현한 내용을 가져와 사용하면된다.

 

이 중에서 바인딩하는 과정을 자세하게 보자

private func bindViewModel() {
    viewModel.users
        .bind(to: tableView.rx.items(cellIdentifier: "UserCell")) { index, user, cell in
            cell.textLabel?.text = user.login
        }
        .disposed(by: disposeBag)
}

viewModel.users.bind(to: tableView.rx.items(cellIdentifier:)) : VM의 users 속성을 테이블 뷰의 데이터 소스로 바인딩 한다.

 

이전에 사용했던 dataSource방식이 아닌 이렇게 RxSwift 방식을 사용해 넣어두면 데이터 변화에 따라 UI 업데이트가 자동으로 이뤄지게 된다.

 

아직 모든 내용을 다 이해할 수 있는 건 아니지만, 이전에 해왔던 방식을 조금 더 효율적으로 할 수 있다는 점이 매우 흥미로운 것 같다.

앞으로도 꾸준히 공부해보자