[iOS] ⭕️ RxSwift(1) - RxSwift 개요의 모든 것 (v.1.3)

Madeline👩🏻‍💻·2024년 3월 27일

RxSwift study

목록 보기
4/6
post-thumbnail

Rx가 뭐야?

Reactive Programming은 개발자들 사이에서 점점 인기를 끌고 있는 프로그래밍 패러다임 중 하나이다.
Rx는 또 'Reactive Extensions', 비동기 데이터 스트림을 처리하는 API의 집합을 말한다.

RxSwift는 Swift언어에 맞게 구현한 라이브러리로, 비동기 코드를 쉽고 간결하게 작성할 수 있게 해주고, UI Event 처리, 네트워크 요청 등 다양한 비동기 작업을 용이하게 한다.

🧐
뭔 말인가?? 배경부터 찬찬히 알아보자.

1. Rx vs Combine

Rx는 Microsoft에서 만든 라이브러리로, UI 친화적인 extension으로 RxSwift는 RxCocoa를 포함한다.

근데 Combine은 애플이 iOS 13이상에서 지원하는 자체적인 반응형 프로그래밍 라이브러리이다.

둘 다 비동기 데이터 스트림을 처리하고, 반응형 프로그래밍을 구현하기 위해 사용되지만,

RxSwift는 다양한 플랫폼에서 사용할 수 있는 반면,
Combine은 Apple 생태계에 최적화되어 있어 iOS, macOS, watchOS, tvOS 등에서 더 효율적으로 동작한다.
Combine은 Swift의 최신 기능들을 활용하여 더욱 간결하고 강력한 API를 제공하며, 성능 면에서도 최적화되어 있다.

RxSwift의 개념을 이해하면 Combine의 개념을 쉽게 이해할 수 있다!

2. 👩🏻‍💻 Async Programming

비동기 프로그래밍은 동기 프로그래밍이랑 반대되는 키워드로,
여러 작업이 동시에 발생할 수 있도록 한다.

예를 들어, 로그인 화면과 기능이 있다고 생각해보자!!

사용자가 아이디, 비밀번호, 버튼, ... 다양한 순서로 작업을 수행할 수 있다.
이런 UI Event(버튼 클릭, 텍스트 입력 ...)은 사용자의 인터랙션에 의해 순서가 보장되지 않으므로, 비동기적으로 처리된다.

그러니까,
유저가 버튼을 누르던지, 아이디에 텍스트를 입력하던지, 하는 이런 이벤트들은 순서가 없다(==비동기적이다)!

💡 기존 비동기 프로그래밍에는 여러가지 방법이 있었다.

  • 클로저
  • Grand Central Dispatch (GCD)
  • Delegate Pattern
  • NotificationCenter

여기에 Rx가 추가된다고 생각하자!

3. Observable 😳 과 Observer 👀

위에서 살펴본 것처럼,
버튼 누른다??
아이디 입력한다??
이런 이벤트들은 순서가 없이, 비동기적으로 동작하기 때문에,
얘네들을 각각 관찰하고 있어야, 이벤트가 일어났음을 알고 바로 이벤트를 처리할 수 있다.
여기에서 관찰 당하는 애가 Observable, 관찰해서 일 처리하는 애가 Observer이다.

RxSwift에서는 이벤트를 전달하는 역할을 하는 것을 Observable,
이벤트를 받아서 처리하는 역할을 하는 것을 Observer 라고 한다.

그러니까, 같은 말이지만,
Observable은 관찰 가능한 형태이고,(관찰 당하는 애)
Observer는 그 이벤트를 관찰하고, 이벤트를 받아서 처리한다.

이 둘의 관계는 생산자소비자 관계로 비유할 수 있다.

Observable은 다음과 같은 과정을 거쳐 이벤트를 전달한다:

  1. 이벤트를 생성하고 방출한다.
  2. Observer에 의해 이벤트가 수신되고 처리된다.

Observable

Observable을 정의한 코드를 살펴보자.

/// A type-erased `ObservableType`. 
///
/// It represents a push style sequence.
public class Observable**<Element>** : **ObservableType** {
    init() {
#if TRACE_RESOURCES
        _ = Resources.incrementTotal()
#endif
    }
    
    public func subscribe<Observer: ObserverType>(_ observer: Observer) -> Disposable where Observer.Element == Element {
        rxAbstractMethod()
    }
    
    public func asObservable() -> Observable<Element> { self }
    
    deinit {
#if TRACE_RESOURCES
        _ = Resources.decrementTotal()
#endif
    }
}

제네릭 클래스로 이루어져있다.
이게 뭐냐!

우리가 비동기 이벤트를,

Observable로 만든다
== 어떤 관찰 가능한 형태로 만든다
== 제네릭 타입의 Observable이라는 클래스 인스턴스를 만든다!

4. Subscribe

그리고 구독! Subscribe을 해주면서 이벤트 전달이 시작되고, Dispose(구독 취소)로 종료된다.

이 과정을 스트림(Stream) 또는 시퀀스(Sequence) 라고 한다.
예를 들어,
계산기 앱에서는 숫자나 기호 버튼 클릭 이벤트를 Observable이 전달하고, Observer가 이를 받아 화면에 결과를 표시하거나 다른 처리를 한다.

데이터의 흐름(stream, sequence): 시간에 따라서 달라지는 데이터들!

같은 말인데, 비동기 이벤트는 해당 이벤트에 대한 항목을 순서대로 방출한다.
그 순서는 보장❌안되어있고, 이벤트도 언제 일어날지 모르지만, Observable은 그 이벤트에 대한 1트, 2트, 3트, ..이런식으로 순서대로 방출한다는 뜻이다!
Observable은 비동기 이벤트가 일어났을 때, 이걸 알리기 위해서 이벤트에 대한 항목을 sequence로 방출한다.

subscribe 메서드도 정의 코드로 살펴보자.


public func subscribe(_ on: @escaping (Event<Element>) -> Void) -> Disposable {
        **let observer = AnonymousObserver** { e in
            on(e)
        }
        **return self.asObservable().subscribe(observer)**
    }
    
public func subscribe<Object: AnyObject>(
        with object: Object,
        **onNext**: ((Object, Element) -> Void)? = nil,
        **onError**: ((Object, Swift.Error) -> Void)? = nil,
        **onCompleted**: ((Object) -> Void)? = nil,
        **onDisposed**: ((Object) -> Void)? = nil
    ) -> Disposable {
        subscribe(
            onNext: { [weak object] in
                guard let object = object else { return }
                onNext?(object, $0)
            },
            onError: { [weak object] in
                guard let object = object else { return }
                onError?(object, $0)
            },
            onCompleted: { [weak object] in
                guard let object = object else { return }
                onCompleted?(object)
            },
            onDisposed: { [weak object] in
                guard let object = object else { return }
                onDisposed?(object)
            }
        )
    }

각 이벤트에 대한 처리를 정의해두었고, onNext, onError, onCompleted는 모두 클로저로 이루어져있다.

그래서 Observable이 이벤트를 방출할 때, subscribe 메서드와 함께 이벤트 방출 이후, 그 다음 작업을 클로저로 전달한다. 누구한테? Observer한테!

구독!!! 좋아요!?

구독(subscribe)한다는 것은
곧 Observer를 생성하고,

그 Observer를 통해 이벤트를 처리하는 것을 의미한다.

내부 코드를 타고타고 가다보면 이벤트 핸들러 클로저까지 볼 수 있다!!!

다시 정리하면,

1. Observer 생성

subscribe 메서드를 호출하면 RxSwift는 내부적으로 Observer를 생성한다. 이 Observer는 이벤트를 처리할 수 있는 클로저를 포함하고 있다.

let observer = AnonymousObserver

->

// AnonymousObserver
final class AnonymousObserver<Element>: ObserverBase<Element> {
    typealias EventHandler = (Event<Element>) -> Void

2. Observer와 Observable 연결

생성된 Observer는 Observable에 연결된다. 이 과정에서 Observable은 이벤트가 발생할 때 Observer에게 전달할 준비를 한다.

return self.asObservable().subscribe(observer)

3. 이벤트 발생 및 전달

Observable에서 이벤트가 발생하면, 이 이벤트는 연결된 Observer에게 전달된다.
Observer는 전달받은 이벤트를 eventHandler 클로저를 통해 처리한다.

// AnonymousObserver
    override func onCore(_ event: Event<Element>) {
        self.eventHandler(event)
    }

무한(Infinite), 유한(Finite)

  • 무한한 Observable Sequence: 계속 이벤트를 전달할 수 있다.
    -> UI와 관련된 이벤트(유저가 뭘 입력하든 반응을 줘야된다!)

  • 유한한 Observable Sequence: 계속 이벤트를 전달할 수 있다.
    -> 대용량 파일을 다운로드 받는다(다운로드 다 하면 끝)

🎃 5. Event

  • Observable이 방출할 수 있는 세 가지 유형의 이벤트
    • 1) Next: 새로운 데이터를 포함한다.-> Emit: 이벤트 방출(==전달)
      (점진적 다운로드, 최신 데이터 전달)
      (데이터 append, remove, ...)
    • 2) Completed: 이벤트 시퀀스가 성공적으로 끝났음! -> Notification
      (ImageView의 Image를 킹피셔로 받아온다던지..)
    • 3) Error: 오류가 발생했음 -> Notification
      (디코딩 실패, 상태코드 오류, 네트워크 연결 유실..)

그래서 사실 Infinite Sequence는 무한한 시퀀스니까, Next로만 이벤트 전달을 하고,
Finite Sequence에서 Next, Error, Complete으로 이벤트 전달함.

📣 Notification: 이벤트를 다시 받을 일이 없으면 노티를 주는 것 -> Dispose 처리
-> Observer가 이벤트 처리를 더이상 안하게 되고 -> 다시 구독하고 -> ...

Observable Lifecycle

1) Observable을 생성한다(이벤트 보내는 놈)
2) Subscribe가 되면, Observable이 실행된다.
3) next로 이벤트를 방출한다.(==emit, 전달)
4-1) if 오류 발생: error 이벤트, else: compeleted 이벤트 Notification
-> Sequence 종료(Observable 재사용 불가능)
4-2) ... deinit되는 시점, Sequence가 종료되는 시점에 Dispose

🎒6. Dispose, DisposeBag (구독 취소)

  • 구독을 관리하고 메모리 누수를 방지하기 위해 사용된다. 구독이 더 이상 필요 없을 때 자동으로 구독을 해제한다.

언제 Dispose?

complete, error 이벤트가 전달되면 바로 Dispose !!

근데 next 이벤트는? Infinite sequence일때는?

=> View 클래스가 deinit되는 시점에 자동으로 dispose, 리소스가 정리된다.

extension Disposable {
    /// Adds `self` to `bag`
    ///
    /// - parameter bag: `DisposeBag` to add `self` to.
    public func disposed(by bag: DisposeBag) {
        bag.insert(self)
    }
}

DisposeBag은 뭐야?

위에서 dispose의 역할이 "메모리 누수를 방지하기 위해!!" 자동으로 구독을 해제하는 것!이라고 다뤘다.
Dispose Bag은 말 그대로, 이런 disposable들을 줍줍해서 담는 가방이다.

구독 취소!하고 싶은 Observable이 많아지면,
disposdBag 가방에 담아두고 한번에 구독 취소 하는거다.

private var disposables = [Disposable]()

요렇게 배열 가방에 줍줍한 것들을 담아서 한번에 구독 취소하는 것!!

-> 정확히는 DisposeBag이 deinit될 때 배열에 있는 모든 Disposable 객체들을 dispose 한다!

좀 자세히 추가해두었지만,
https://velog.io/@maddie/iOS-RxSwift1-1-Dispose%EA%B0%80-%EB%AD%98%EA%B9%8C
다음 글에서 이어집니다~~

7. Operator

자주 쓰던 반복문이나 조건문 말고 새로운 Rx만의 관점에서 바라보자!

  • just, from, of는 Observable을 생성하는 여러 방법(Operator)
    • just: 단일 요소를 Observable로 변환한다.
      (list를 모두 출력한다)
    • from: 배열과 같은 시퀀스를 Observable 시퀀스로 변환한다.
    • of: 여러 요소를 가지고 Observable 시퀀스를 생성한다.

-> 연산자 관련 내용은 다음 포스트에 자세히 있습니다~!~!!~

RxSwift

Rx프로토콜은 RxSwift로 우리가 코드를 구성할 수 있겠는데,
이 프로토콜을 살펴보면 마지막에

import Foundation

/// Extend NSObject with `rx` proxy.
extension NSObject: ReactiveCompatible { }

결국 ViewController, UIView, ...의 제일 최상위 부모뷰인 NSObject에서 이 프로토콜을 쓴다. 그래서 모든 UI 요소에 rx를 붙여서 접근할 수 있다.

예시 코드

UIKit에 RxSwift를 사용한 간단한 예시이다.
UIButton의 탭 이벤트를 구독하고 UILabel에 표시해보자.

import UIKit
import RxSwift
import RxCocoa

class ViewController: UIViewController {
    var button: UIButton!
    var label: UILabel!
    var disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Observable 생성
        button.rx.tap
            .subscribe(onNext: { [weak self] _ in
                self?.label.text = "Button tapped!"
            })
            .disposed(by: disposeBag)
    }
}
  • 여기서 button.rx.tapObservable!
    사용자가 버튼을 탭할 때마다 next 이벤트를 방출한다.

  • subscribe(onNext: { ... })Observer!
    Observable이 방출하는 이벤트를 받아서 처리한다. 여기서는 레이블의 텍스트를 변경한다.

  • 마지막으로, disposed(by: disposeBag)을 사용하여 구독을 관리하고, 뷰 컨트롤러가 사라질 때 자동으로 구독을 해제한다.

  • 모든 Rx의 끝은 저 가방으로 끝날 것..

  • UI Event는 위에서 배운대로, Infinite Sequence여서 onError, onComplete는 쓰지 않아도 되겠다.

    요 on만 있는 애가 UIEvent(Infinite Sequence 어쩌고) 이다!

공부 2트 (v.1.1)

저도 공부를 비동기적으로 하고 있는데 말이죠ㅎㅎ
제 블로그를 관찰하고 계시다면 당근을 흔들어주세요. 🥕

위에서 당당하게
여기서 button.rx.tapObservable!
라고 써놨길래 업데이트 해보겠습니다.

엄밀히 따져보면,

button.rx.tap.subscribe 어쩌고

가 있다면,
button.rx.tap <- 은 ControlEvent 타입입니다.

ControlEventControlEventType 프로토콜을 채택하고 있는 구조체인데,
ControlEvent 안에는
events라는 Observable도 있고,
이벤트가 방출되면 실행되는 init 구문으로, 그 이벤트에 대한 Observable을 생성해서 events 프로퍼티에 넣어주는 코드도 들어있답니다.

그래서 button.rx.tapObservable인가?
아주 그렇지는 않고, ControlEvent를 반환하는데, 그 ControlEventevents 프로퍼티가 Observable이다!라고 볼 수 있겠습니다.

그러면, button.rx.tap.subscribe 어쩌고는 뭔가?
subscribe는 아까 Observable 클래스에 있던 메서드 아닌가??
그러면 controlevent 뒤에 observable 클래스 메서드를 쓰는건가??
짬뽕인가??
싶지만,
Observable: ObservableType
ControlEvent: ControlEventType: ObservableType
결국 공통적으로 ObservableType 프로토콜을 채택하고 있다.
subscribeObservable의 메서드가 아니라, ObservableType 프로토콜에 구현되어 있기 때문에,
button.rx.tap.subscribe가 가능하다고 보는 게 좋겠다.

그리고 투비 컨티뉴

레퍼런스

샤라웃 투 소들이님
https://babbab2.tistory.com/185

profile
🍎 Apple Developer Academy@POSTECH 2기, 🍀 SeSAC iOS 4기

0개의 댓글