
RxSwift를 이해하기 위해서는 먼저 반응형 프로그래밍을 이해하면 좋다.
반응형 프로그래밍은 데이터의 흐름과 변경 사항을 전파하는 데에 중점을 둔 프로그래밍 패러다임으로, 데이터에 어떤 변경이 이루어졌을 때(즉, 이벤트가 발생했을 때) 연결된 실행 모델들이 데이터를 전파받아 특정 작업을 수행한다.
엥 근데 이거 Observable로 이미 하고 있던 거 아닙니까
라고 생각할 수 있는데 그렇긴 하다(;;) 근데 이제 RxSwift 라이브러리를 활용하지 않고 Observable Pattern을 사용해 반응형 프로그래밍을 구현했을 때의 단점은
정도가 되겠다. 그리고 RxSwift는 Observable Pattern의 이러한 한계점들을 모두 해결해줄 수 있다고 하는데 ... 이 부분을 이해하기 위해 ReactiveX의 공식 설명 중 일부를 인용해보면 다음과 같다.
ReactiveX is a library for composing asynchronous and event-based programs by using observable sequences.
ReactiveX는 관찰 가능한 시퀀스를 사용하여 비동기 및 이벤트 기반 프로그램을 작성하기 위한 라이브러리입니다.
말이 어려워 보이지만 결국은 어떠한 비동기 이벤트를 관찰 가능한 형태로 만든다는 점 정도만 이해하면 된다.
이후에는 비동기 이벤트가 발생하면 함수 형태의 연산자를 이용해 전파받을 수 있고, RxSwift 라이브러리 내에 있는 여러 메서드들을 통해 다양한 연산들을 처리할 수 있다.
위에서 RxSwift는 어떠한 비동기 이벤트를 관찰 가능한 형태로 만드는 것이라고 언급했는데,이렇게 관찰 가능한 형태 자체를 바로 Observable이라고 한다.
이 Observable은 관찰이 가능한 흐름이며 비동기 이벤트의 시퀀스를 생성할 수 있는 대상이다.
public class Observable<Element> : ObservableType {
init() {
#if TRACE_RESOURCES
_ = Resources.incrementTotal()
#endif
}
즉 이벤트를 관찰 가능한 형태로 만든다는 건 Observable 타입의 인스턴스를 생성하는 것과 같다.
엥 근데 시퀀스? 가 뭔데요?
위에서 Observable이 비동기 이벤트의 시퀀스를 생성할 수 있는 대상이라고 언급했었다.
먼저 시퀀스에 대해 이해하기 전에 Observable 객체가 이벤트가 발생했을 때 item이라는 것을 방출한다는 사실을 알아야 한다.
이런 이벤트가 연속적으로 발생하게 되면 Observable 객체는 여러 개의 item을 순차적으로 방출하는데, 이 순차적인 흐름을 시퀀스라고 칭한다.
다시 한번 설명하자면 시퀀스는 연속적인 이벤트 상에서 방출되는 item의 순차적인 흐름이라는 것! 직관적으로 이해하면 쉽다.
Observable이 비동기 이벤트의 시퀀스를 생성한다는 말은 곧 시간의 흐름에 따라 여러 개의 item들을 방출한다는 말이다.
방출만 하면 아무래도 의미가 없기 때문에 ... 이 item들을 방출했을 때 받을 수 있는 객체가 필요하다.
바로 그게 Observer라는 말씀!
Observer는 비동기 이벤트가 발생했을 때 전파받을 수 있는 객체로서, Observable을 구독한다.
즉, Observer는 특정 비동기 이벤트의 시퀀스를 생성하는 Observable 객체를 구독할 수 있다!
Observable 객체를 구독하게 되면 이후 비동기 이벤트가 발생했을 때 방출되는 해당 item을 받아올 수 있다.
그러면 어떻게 구독할 수 있는지를 또 알아봐야 하는데 ... 그게 바로 Subscribe, 즉 구독이다.
The
Subscribeoperator is the glue that connects an observer to anObservable. In order for an observer to see the items being emitted by anObservable, or to receive error or completed notifications from the Observable, it must first subscribe to thatObservablewith this operator.
Subscribe오퍼레이터는 옵저버와Observable을 연결하는 접착제 역할을 합니다. 옵저버가Observable이 방출하는 항목을 보거나Observable로부터 오류 또는 완료 알림을 받으려면 먼저 이 오퍼레이터를 사용하여 해당Observable을 구독해야 합니다.
RxSwift의 subscribe() 메소드에는 총 4개의 파라미터가 있고, 이 파라미터들을 통해 특정 상황에서 어떤 작업을 할건지 넘겨줄 수 있다.
public func subscribe(
onNext: ((Element) -> Void)? = nil,
onError: ((Swift.Error) -> Void)? = nil,
onCompleted: (() -> Void)? = nil,
onDisposed: (() -> Void)? = nil
) -> Disposable
onNext는 Observable 객체에서 새로운 item이 방출됐을 때 실행된다.onError는 Observable 객체에서 기대하는 데이터가 생성되지 않았거나 오류가 발생했을 때 실행된다.onCompleted는 Observable 객체의 이벤트가 종료되어 더 이상 호출되지 않을 때 실행된다.onDisposed도 있는데 일단 넘어가 보자!button.rx
.tap
.subscribe(onNext: {
print("🚨 웨에에엥 item이 성공적으로 방출됐지롱 🚨")
},
onError: { error in
print("🚨 에에에엥 에러발생 에러발생 🚨")
},
onCompleted: {
print("🚨 애에에엥 이벤트가 끝났지롱 🚨")
})
.disposed(by: disposedBag)
실제로는 위와 같이 사용한다.
subscribe() 메소드를 사용할 경우 메소드 내에서 자체적으로 AnonymousObserver 타입의 객체를 생성하고 자체적으로 Observable 객체에 구독을 해준다. 메소드 하나만으로 구독이 된다니 아주 간편하다 ㅎㅎ
Dispose는 처리하다, 없애다 라는 뜻을 가지고 있다!
Disposable는 이런 뜻에 맞게 Observable 객체를 구독 해제하고 싶을 때 사용할 수 있다.
public func subscribe(
onNext: ((Element) -> Void)? = nil,
onError: ((Swift.Error) -> Void)? = nil,
onCompleted: (() -> Void)? = nil,
onDisposed: (() -> Void)? = nil
) -> Disposable
위에서 살펴본 subscribe() 메소드 원형을 다시 살펴보면 리턴형이 Disposable인 것을 확인할 수 있다!
subscribe() 메소드를 호출할 경우 Observable 객체를 구독할 수 있는데, 해당 Observable 객체의 이벤트를 더 이상 구독하지 않아도 될 경우(= 이벤트가 끝났을 경우) Disposable 타입의 값을 리턴해 구독을 취소시켜 준다.
/// Represents a disposable resource.
public protocol Disposable {
/// Dispose resource.
func dispose()
}
Disposable 프로토콜을 뜯어보면 이렇게 dispose() 라는 메서드가 정의되어 있다.
결론적으로 정리하면
subscribe() 메서드를 사용한다. subscribe() 메서드는 구독한 Observable 객체의 이벤트가 모두 끝나면 Disposable 타입의 인스턴스를 리턴한다.dispose()를 호출하면 구독한 Observable 객체에 대한 구독을 해제할 수 있다.이렇게 구독을 해제하는 건 사실 메모리 관리 측면에서 큰 의미가 있다.
만약 구독을 해제해주지 않으면 Observable 객체가 이벤트가 발생할 때마다 계속 item을 방출할 우려가 있고, 이로 인한 메모리 릭(Memory Leak)도 발생할 수 있다!
그래서 dispose()를 통해 구독 해제를 해주는 거다.
근데 그럼 만약 여러 Observable 객체를 만들어서 구독해야 하는 리소스가 많아지면 어떻게 될까?
일단 인스턴스 자체도 여러 개 선언해야 하고, 코드가 늘어나고 ... 가정이 무너지고 사회가 무너지고
그래서 Disposable 타입의 인스턴스들을 담을 수 있는 DisposeBag이라는 배열을 사용한다.
extension Disposable {
/// Adds `self` to `bag`
///
/// - parameter bag: `DisposeBag` to add `self` to.
public func disposed(by bag: DisposeBag) {
bag.insert(self)
}
}
위 코드를 보면 disposed()는 DisposeBag 객체에 Disposable 객체를 추가해주는 메서드라는 것을 확인할 수 있다.
class ViewController: BaseViewController {
private let disposeBag = DisposeBag()
private lazy var textField: UITextField = UITextField().then {
$0.placeholder = "이름을 입력해보시지"
$0.backgroundColor = .lightGray
$0.layer.cornerRadius = 8
}
...
override func setupAction() {
textField.rx.text
.subscribe(onNext: { text in
guard let input = text else { return }
print("지금 텍스트필드는 \(input)")
})
.disposed(by: disposeBag)
}
}
위 코드에서는 전역 변수로 DisposeBag 타입의 인스턴스를 선언하고 있고, 텍스트필드의 텍스트 이벤트에 대해서 실행할 클로저를 subscribe()로 선언하고 반환값인 Disposable 타입의 인스턴스를 disposeBag 안에 담아줬다!
근데 여기서 놀라운 점!
ViewController 의 생명주기가 끝날 때 어떠한 작업을 하지 않아도 알아서 dispose() 가 된다.
public final class DisposeBag: DisposeBase {
...
deinit {
self.dispose()
}
}
왜인지는 DisposeBag 클래스를 뜯어보면 알 수 있는데, DisposeBag 인스턴스 자체가 deinit 되면 자동으로 dispose() 가 실행되도록 되어 있다.
/// This is internal on purpose, take a look at `CompositeDisposable` instead.
private func dispose() {
let oldDisposables = self._dispose()
for disposable in oldDisposables {
disposable.dispose()
}
}
deinit될 때 호출되는 dispose() 메서드를 뜯어보면 또 자동으로 DisposeBag 안에 담긴 disposable 객체들을 순회하며 하나씩 dispose() 를 해주는 걸 볼 수 있다.
그래서 이 모든 것을 정리한 결론은
- Observable 객체를 선언하고 하나하나 메모리 릭 안나게 관리할 자신이 없다면
subscribe()호출할 때disposed()메서드를 사용해서 DisposeBag 안에 담아놓으면 된다.- 그러면 DisposeBag 이 알아서 메모리 릭 안나게 deinit할 때 구독 해제 해주니까~
끝!
Observable Pattern을 적용한 뒤에 RxSwift를 배워보니 일단 정말 편리하다는 건 알겠다 ... 스레드도 안정적으로 운용할 수 있고 메모리도 자동으로 관리해준다고 하니 빨리 적용해보고 싶은 기분 ㅎㅎ
프로젝트를 RxSwift로 리팩토링 할건데 빨리 적용해봐야겠다.
RxSwift) RxSwift가 도대체 뭔데요(Reactive Programming)
RxSwift) Observable, Observer, Subscribe 흐름 이해하기
RxSwift) Dispose /Disposable / DisposeBag 이해하기