iOS - RxSwift

이한솔·6일 전
0

iOS 앱개발 🍏

목록 보기
47/49

RxSwift가 왜 생겼을까?

반응형 프로그래밍

기존에는 Completion Handler나 Delegate 패턴을 사용하여 비동기적인 작업을 처리했지만, 이러한 방식은 비동기 코드가 복잡해지고 중첩된 구조가 생길 수 있으며, 코드의 가독성과 유지보수성을 저하시킬 수 있다.

반응형 프로그래밍은 이러한 문제를 해결하기 위해 등장했다. 이 접근 방식은 데이터 스트림을 중심으로 하여 데이터의 발생과 처리를 자연스럽게 통합할 수 있습니다.
데이터 스트림 은 데이터가 연속적으로 발생하는 시퀀스를 의미하며, 이를 Observable이라는 개념으로 표현한다. Observable은 데이터를 생성하고 변환하여 Observer가 구독할 수 있도록 제공한다.

RxSwift (Reactive Extension + Swift)

RxSwift는 ReactiveX 프레임워크의 일부로서, 반응형 프로그래밍의 개념을 Swift 언어에 적용한 것이다. Observable을 통해 데이터 스트림을 정의하고, 이를 Observer가 구독하여 데이터의 변화에 반응하도록 한다. 이를 통해 비동기 데이터 스트림을 간결하게 처리하고, 복잡한 비동기 코드를 동기적으로 처리하는 것처럼 보일 수 있다. 이러한 접근은 코드의 복잡성을 줄이고, 가독성을 높이며, 유지보수성을 개선하는 데 도움을 준다.

💡 ReactiveX?
ReactiveX(Reactive Extensions)는 다양한 프로그래밍 언어에서 사용할 수 있는 함수형 프로그래밍 스타일을 적용한 라이브러리로, 이 프레임워크는 데이터 스트림을 쉽게 다룰 수 있도록 설계되었으며, 비동기 및 이벤트 기반 프로그래밍을 위한 도구를 제공한다.
ReactiveX



Observable

Observable은 데이터 스트림을 표현하는데 사용되며, 데이터의 발생과 변화를 관찰할 수 있는 객체이다. Observable 클래스의 기본적인 구현 예제를 보자!
ObservableType 프로토콜을 준수하는 Observable이라는 제네릭 클래스이다.

// 비동기 작업을 수행하고 그 결과를 관찰자(observer)에게 전달하는 클래스
class Observable<T> {
    // task는 클로저로, T 타입의 데이터를 비동기적으로 생성하고 completion handler를 호출한다.
    private let task: (@escaping (T) -> Void) -> Void
    
    // 초기화 메서드로, task 클로저를 받아 저장한다.
    init(task: @escaping (@escaping (T) -> Void) -> Void) {
        self.task = task
    }
    
    // subscribe 메서드는 observer가 호출할 클로저를 인자로 받는다.
    func subscribe(_ f: @escaping (T) -> Void) {
        // task 클로저를 호출하여 비동기 작업을 시작하고, 작업이 완료되면 데이터를 전달한다.
        task(f)
    }
}

// Observable 생성
let observable = Observable<Int> { completion in 
    DispatchQueue.global().async {
        completion(42) // 비동기 작업 결과로 42를 완료 핸들러에 전달
    }
}

// Observable 구독
observable.subscribe { result in 
    print(result) // 받은 데이터를 출력
}

Observable 생성 과정

  1. Observable.create() 메서드를 사용하여 Observable을 생성한다.
    create() 메서드는 클로저를 파라미터로 받으며, 이 클로저는 Observable이 구독될 때 실행된다.

  2. observer.onNext()를 통해 데이터를 전달한다.

  3. Observable의 작업이 완료되면 observer.onCompleted()를 호출하여 데이터 스트림이 종료됨을 알린다. 작업 도중 에러가 발생한 경우에는 observer.onError(error)를 호출하여 에러를 처리할 수 있다.
    OnCompleted 또는 OnError가 호출되면 Observable은 종료된다.

  4. Observable을 구독한 Subscriber가 더 이상 데이터를 받고 싶지 않을 때, Observable은 Disposables.create()를 통해 Disposable을 반환하여 자원을 정리하고 구독을 해제합니다.

비동기로 생기는 데이터를 Observable로 감싸서 Return 하는 코드 예제이다.

import RxSwift

// 비동기 작업을 수행하고 그 결과를 관찰자(observer)에게 전달하는 함수
func downloadJson(_ url: String) -> Observable<String?> {
    // Observable 생성
    return Observable.create { observer in
        // 비동기 작업 수행 (예: 네트워크 요청)
        DispatchQueue.global().async {
            // 실제로는 네트워크 요청을 통해 데이터를 받아오거나 처리할 수 있습니다.
            // 여기서는 간단히 "Hello"라는 문자열을 방출하고 완료를 알립니다.
            
            // 데이터 전달 (방출)
            observer.onNext("Hello")
            
            // 작업 완료 후 Observable 종료
            observer.onCompleted()
        }
        
        // Disposable 반환을 통해 구독 해제 및 자원 정리
        return Disposables.create()
    }
}

Observable의 생명 주기

Observable의 생명 주기는 Create -> Subscribe -> OnNext / OnError(종료) -> OnCompleted(종료) -> Disposed(종료) 순서로 진행된다.

Observable이 생성되고 구독자(Subscriber)가 등록된 후, 데이터가 발생하면 OnNext 이벤트가 발생하고, 작업이 완료되면 OnCompleted 이벤트가 호출됩니다. 구독자가 Dispose하여 구독을 해제하면 Observable은 Disposed 상태로 전환됩니다.

이렇게 RxSwift의 Observable을 사용하면 비동기 작업을 쉽게 관리하고 데이터 스트림을 효과적으로 처리할 수 있습니다.



Observer

Observer는 ObservableType 프로토콜을 채택해서 프로토콜에 구현되어있는 subscribe 메서드를 통해 Observable을 구독하고, Observable로부터 데이터를 수신하고 처리하는 역할을 한다. 또한, Disposable을 이용하여 구독을 해제하고, 리소스를 해제하는 등의 작업을 처리할 수 있다.

Observer의 데이터 처리 메소드

Observer는 Observable이 방출하는 값들을 받거나 처리하는 onNext, onError, onCompleted 메서드가 있다. subscribe 메서드 내에서 이 메서드들을 클로저 형태로 정의하여 데이터를 처리한다.

onNext: Observable이 데이터를 방출할 때 호출된다. 이 메서드는 Observable이 방출하는 각 데이터 값을 받아와서 처리하는 로직을 구현한다.

onError: Observable이 에러를 방출할 때 호출된다. 에러가 발생하면 onNext와 onCompleted는 호출되지 않는다.

onCompleted: Observable이 정상적으로 완료될 때 호출된다. 이 메서드는 Observable이 모든 데이터 방출을 완료하고 추가적인 처리가 필요한 경우에 사용된다.

Subscribe 과정

  1. Observer가 Observable을 구독
  2. Observable이 데이터를 방출할 때마다 Observer가 이를 수신하고 처리
func onLoad() {
    // Observable 생성 (예: 비동기 작업을 수행하는 downloadJson 함수 호출)
    let observer = downloadJson(url: "http://example.com/json")
    
    // Observable을 구독하여 데이터 수신
    let disposable = observer.subscribe { event in
        switch event {
        case .next(let json):
            // 데이터가 방출될 때 호출
            print(json)
        case .completed:
            // 작업이 완료되었을 때 호출
            print("Completed")
        case .error(let error):
            // 에러가 발생했을 때 호출
            print("Error: \(error)")
        }
    }
    
   // subscribe하고 결과가 return 될때 Disposable도 같이 returne됨
   // 필요에 따라 원하는 시점에 취소 가능 
   // 예를 들면, 변수로 var disposable: Disposable? 선언 후 viewWillAppear시 disposable.dispose()로 취소 가능
.dispose() 
     disposable.dispose()
}


Disposable

Observable은 기본적으로 complete이나 error가 발생하기 전까지 계속 이벤트를 방출한다. 따라서, 이벤트가 더 이상 방출되지 않는 시점에서는 직접 해제를 해줘야 한다. Observable을 subscribe할 때 해당 구독을 취소할 수 있는 수단으로 Disposable 타입의 값이 반환된다. 이를 통해 구독을 관리하고, 메모리를 효율적으로 관리할 수 있다. 예를 들어, subscribe 후 반환된 Disposable 값을 변수로 저장한 다음, 뷰컨트롤러가 deinit될 때 dispose 메소드를 호출하여 메모리 관리를 할 수 있다.

import RxSwift

class MyViewController: UIViewController {
    private var disposable: Disposable?
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let observable = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
        
        // subscribe 후 반환된 disposable 변수로 저장 
        disposable = observable.subscribe(onNext: { value in
            print("Received value: \(value)")
        }, onCompleted: {
            print("Completed")
        }, onError: { error in
            print("Error: \(error)")
        })
    }
    
    // 뷰컨트롤러 deinit시 dispose() 실행
    deinit {
        disposable?.dispose()
        print("MyViewController deinitialized and subscription disposed.")
    }
}

DisposeBag

DisposeBag은 여러 Disposable을 한곳에 담아서 관리할 수 있는 컨테이너라고 생각하면 된다. DisposeBag 객체는 deinit될때 자동으로 dispose 메소드를 호출해 메모리 관리를 해줄 수 있다.
예를 들어, DisposeBag을 전역 프로퍼티로 선언해준 경우, 해당 DisposeBag을 담고 있는 인스턴스가 사라질 때 DisposeBag이란 프로퍼티도 같이 deinit이 되고, 이때 Disposable의 배열을 순회하며 dispose 메서드를 호출해주기 때문에 각각의 Observer에 별도로 dispose 메소드를 호출해 주지 않아도 여러 구독을 한 번에 해제할 수 있다.

import RxSwift

private var disposeBag = DisposeBag()

func setupBindings() {
    let observable = Observable.just("Hello, RxSwift!")
    
    observable.subscribe(onNext: { value in
        print(value)
    }).disposed(by: disposeBag)
}


참고자료1
참고자료2

0개의 댓글