RxSwift를 공부하면서, 매번 갈증이 있었던 것이 있습니다. 지금 제 상태는 What에 대한 답은 할 수 있습니다. Rxswift가 무엇인지 그 자체에 대해서는 a = b 이다
처럼 이야기할 수 있습니다. 다만 경험이 부족하므로 어떤 상황과 맥락에서 어떻게 사용해야할지에 대한 판단이 느립니다. 그래서 When과 How에 대한 질문에 빠르게 답변하고 싶어서 이렇게 오늘 공부합니다.
RxSwift는 값이 변경되면, 그 값에 맞게 새롭게 연산해서 결과를 처리하고 UI까지 자동으로 변경되었으면 하는 니즈를 충족해줍니다.
기존에 아래와 같은 코드가 있다고 해봅시다.
var x = 1
var y = 2
print(x + y) // 3
a = 98
a = 98
이 되었지만 위에 있는 코드는 자동으로 다시 연산하지 않습니다. 즉, 값은 변했으나, 연산결과는 아직 변경되지 않은 상태죠.이러한 코드에 Rx를 적용해보겠습니다.
let bag = DisposeBag()
let x = BehaviorSubject(value: 1) // Default value: 1
let y = BehaviorSubject(value: 2) // Default value: 1
Observable.combineLatest(a, b) { $0 + $1 } // 2 개의 파라미터를 더한다.
.subscribe(onNext: { print($0) }) // next 이벤트에 대해 프린트한다.
.disposed(by: bag) // disposeBag에 등록한다.
x.onNext(12) // onNext 이벤트로 12를 전달한다.
Observable.combineLatest
라는 객체를 사용했습니다. 이를 통해서 덧셈연산을 구현했죠. 마치 수식을 세우는 것 같죠?x.onNext
코드에서는 12를 전달하고 있습니다. 그리고 print
문을 전혀 쓰고 있지 않습니다.지금은 아주 단순한 코드입니다. 이것을 어떤 케이스에서 활용할지 고민해보고 싶었습니다.
수량 x 가격
이라는 연산이후에, 이것을 UI로 업데이트 하고 싶다.이런 경우가 있지 않을까요?
이런 질문이 있을 수 있겠죠.
Q. 그런데 그런 것들은 Rx를 안써도 구현가능하지 않나요?
네, 맞습니다. 안써도 가능합니다. 프로퍼티옵저버를 활용하거나 이미 프로토콜에 구현된 메소드 그리고 생명주기 함수를 활용하면 충분히 구현가능합니다. even though, RxSwift 이냐면,
이 외에도 Rx의 장점들이 있겠지만, 위 3 개는 제가 경험에서 느낀 장점입니다. 그러므로
RxSwift는 투자 대비 좋은 혜택을 준다.
로 정리하겠습니다.
결론
1. Observable -> Observer : 이벤트를 전달한다.
2. Observer -> 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
}
}
ObservableType
을 채택하고 있습니다. 그리고 <Element>
타입을 전달해줘야하네요.subscribe
메소드를 통해서 파라미터로 업저버를 전달해주면 되겠네요.(물론 Observer
타입을 준수하는 객체어야겠죠.)옵저버블은 신문사와 신문사 구독과 같다고 생각이 듭니다. 제가 특정 신문을 구독하면, 신문은 사건사고(이벤트)가 있을 때마다 이벤트를 구독자들에게 보내겠죠.(온라인 신문 기준) 그러면 구독자들은 해당 내용을 받을겁니다. 그런식으로 이벤트를 주고 그리고 그 이벤트를 받는 관계가 옵저버블과 옵저버라고 생각됩니다.
그러면 이걸 (When)언제쓰고, (How)어떻게 쓰는지 생각해볼게요.
그러면 이제 어떻게 코드를 작성하는지 알아보겠습니다.
이 순서로 구현해야겠죠.
Observable이 전달하는 이벤트
옵저버블을 생성하는 메소드는 create
라는 연산자를 활용합니다. Rxswift의 코드를 보면 아래와 같이 정의되어 있습니다.
extension ObservableType {
// MARK: create
/**
Creates an observable sequence from a specified subscribe method implementation.
- seealso: [create operator on reactivex.io](http://reactivex.io/documentation/operators/create.html)
- parameter subscribe: Implementation of the resulting observable sequence's `subscribe` method.
- returns: The observable sequence with the specified implementation for the `subscribe` method.
*/
public static func create(_ subscribe: @escaping (AnyObserver<Element>) -> Disposable) -> Observable<Element> {
AnonymousObservable(subscribe)
}
}
Observable
을 리턴하고 있죠?Observable<String>.create { (observer) -> Disposable in
observer.on(.next(0)) // 구독자에게 next 이벤트를 전달한다.
observer.onNext(1) // 구독자에게 next 이벤트를 전달한다.
observer.onCompleted() // 컴플리티드 메소드를 전달하고 옵저버블을 종료한다.
return Disposables.create() // return 타입에 맞게 리턴한다.
}
Observable
의 Element Type을 String으로 했는데, next 이벤트는 Int로 전달하고 있습니다.AnyObserver<Elemnt>
라는 부분의 Element와 Observable의 타입이 다르기 때문에 에러가 발생합니다. 이처럼 옵저버블과 옵저버의 타입을 일치시켜줘야 컴파일에러를 피할 수 있습니다. 그래서 Int로 모두 통일하면 에러가 없겠죠.Observable<Int>.create { (observer) -> Disposable in
observer.on(.next(0)) // 구독자에게 next 이벤트를 전달한다.
observer.onNext(1) // 구독자에게 next 이벤트를 전달한다.
observer.onCompleted() // 컴플리티드 메소드를 전달하고 옵저버블을 종료한다.
return Disposables.create() // return 타입에 맞게 리턴한다.
}
오퍼레이터중에서 from
이라는 연산자를 활용하면 좀더 편리하게 구현가능합니다.
Observable.from([0, 1])
즉, 위에 있는 코드 중에서
observer.on(.next(0))
observer.onNext(1)
obsserver.onCompleted()
이 부분을 코드 한 줄로 구현 가능하다는 뜻입니다.
무언가 다량의 이벤트를 순차대로 전달해야한다고 판단이 든다면 from 을 사용하면 좀 더 편리하게 구현할 수 있겠네요.
여기까지 하면 아무일도 발생하지 않는데요?
네, 여기까지의 코드는 옵저버블을 "생성"만 한 상태입니다. 마치 신문사가 신문회사를 차리기만 한 상태입니다. 아직 신문구독자가 아무도 없으니 신문을(혹은 인터넷기사를) 발행하지 않겠죠. 그러므로, 구독자가 생기면 해당 이벤트를 전달합니다.
위 내용을 보면, 제가 RxSwift의 장점 중 하나인, 코드가 분산된 문제가 해결됩니다
가 보이시나요?
앞으로 옵저버블을 생성한 바로 아래 코드에서 제가 원하는 로직을 모두 작성하면 됩니다. 그리고 그 순서로 일을 처리하고 싶을 때, 구독만 하면 되는 것이죠. 구독과 좋아요 알림설정까지...
개인적으로 이 개념이 처음 볼 때는 너무 생소해서 무슨 말인가 싶었습니다. 그러다가 코드를 작성하면서, 델리게이트 메소드와 프로퍼티 등등 코드들이 분산되는 것이 심해지는 순간을 맛보고 난 이후에, Rx가 코드 가독성을 상당히 올려준다는 것을 느꼈습니다.
이어지는 글에서 이제 구독을 해보겠습니다.
옵저버를 구독하는 방법은 간단합니다. subscribe
메소드를 이용하면 됩니다.
/**
Subscribes an event handler to an observable sequence.
- parameter on: Action to invoke for each event in the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
public func subscribe(_ on: @escaping (Event<Element>) -> Void) -> Disposable {
let observer = AnonymousObserver { e in
on(e)
}
return self.asObservable().subscribe(observer)
}
subscribe의 다른 메소드르 보겠습니다.
/**
Subscribes an element handler, an error handler, a completion handler and disposed handler to an observable sequence.
- parameter onNext: Action to invoke for each element in the observable sequence.
- parameter onError: Action to invoke upon errored termination of the observable sequence.
- parameter onCompleted: Action to invoke upon graceful termination of the observable sequence.
- parameter onDisposed: Action to invoke upon any type of termination of sequence (if the sequence has
gracefully completed, errored, or if the generation is canceled by disposing subscription).
- returns: Subscription object used to unsubscribe from the observable sequence.
*/
public func subscribe(
onNext: ((Element) -> Void)? = nil,
onError: ((Swift.Error) -> Void)? = nil,
onCompleted: (() -> Void)? = nil,
onDisposed: (() -> Void)? = nil
) -> Disposable {
let disposable: Disposable
if let disposed = onDisposed {
disposable = Disposables.create(with: disposed)
}
else {
disposable = Disposables.create()
}
#if DEBUG
let synchronizationTracker = SynchronizationTracker()
#endif
let callStack = Hooks.recordCallStackOnError ? Hooks.customCaptureSubscriptionCallstack() : []
let observer = AnonymousObserver<Element> { event in
#if DEBUG
synchronizationTracker.register(synchronizationErrorMessage: .default)
defer { synchronizationTracker.unregister() }
#endif
switch event {
case .next(let value):
onNext?(value)
case .error(let error):
if let onError = onError {
onError(error)
}
else {
Hooks.defaultErrorHandler(callStack, error)
}
disposable.dispose()
case .completed:
onCompleted?()
disposable.dispose()
}
}
return Disposables.create(
self.asObservable().subscribe(observer),
disposable
)
}
}
onNext
, onError
, onCompleted
그리고 onDisposed
를 입력받고있습니다.이제 코드로 작성해볼게요.
먼저 옵저버블을 생성합니다.
let observable = Observable.from([1, 2, 3])
그리고 해당 옵저버블을 넥스트이벤트만 처리하는 subscribe 메소드로 구독하겠습니다.
observable.subscribe { event in
print("옵저버블: 이벤트를 옵저버에게 전달합니다.")
print(event)
}
결과는 다음과 같습니다.
옵저버블: 이벤트를 옵저버에게 전달합니다.
next(1)
옵저버블: 이벤트를 옵저버에게 전달합니다.
next(2)
옵저버블: 이벤트를 옵저버에게 전달합니다.
next(3)
옵저버블: 이벤트를 옵저버에게 전달합니다.
completed
컴플리티드 부분에서 모든 로직을 처리한 이후에 tableview.reloadData()
같은 로직을 호출하고 싶을 수도 있지 않을까요?
그럴 때, 두 번째 subscribe 메소드를 이용합니다.
observable
.subscribe(
onNext: {
print("옵저버블: 이벤트를 옵저버에게 전달합니다.")
print($0)
},
onError: { _ in print("에러발생")},
onCompleted: { print("종료되었습니다.")},
onDisposed: { print("메모리 해제")})
옵저버블: 이벤트를 옵저버에게 전달합니다.
1
옵저버블: 이벤트를 옵저버에게 전달합니다.
2
옵저버블: 이벤트를 옵저버에게 전달합니다.
3
종료되었습니다.
메모리 해제
정리해보겠습니다.