zip Operator에 대하여(RxSwift)

3 분 소요

zip은 대표적인 Observable 병합 연산자이다.

내가 가진 궁금증은 아래와 같다.
 

Observable.zip을 중첩하여 사용하는 것은 가능할까?

이를테면 아래와 같이 사용이 가능한 것일까?

_ = Observable.zip(
            Observable.just(1).debug("중간결과1"),
            Observable.zip(Observable.just(2), Observable.just(3)) {
                return $0 + $1
            }.debug("중간결과2")
        ) {
            return $0 + $1
        }.debug("결과값")
        .subscribe()

내부에 있는 Observable.zip에 대해서는 subscribe를 한 것도 아니기 때문에 처음에는 안될 것이라고 생각했다. 그러나 결과는 달랐다.

결과값 -> subscribed
중간결과1 -> subscribed
중간결과1 -> Event next(1)
중간결과1 -> Event completed
중간결과1 -> isDisposed
중간결과2 -> subscribed
중간결과2 -> Event next(5)
결과값 -> Event next(6)
중간결과2 -> Event completed
결과값 -> Event completed
결과값 -> isDisposed
중간결과2 -> isDisposed

외부에서 subscribe한 것에 대해 내부에 있는 Observable.zip도 subscribe가 되었다. 결과로 판단하자면, Observable.zip에 대한 subscribe는 단순히 인자로 들어간 시퀀스들을 바라보면서 값이 오나 안오나 보기만 하는게 아니라 내부 인자로 들어간 시퀀스들에 대해 subscribe를 개별적으로 다 한 뒤에 값이 오는지 보는것 이다.

 

바깥 Observable.zip이 dispose되는 시점이 궁금해서 추가적 테스트를 해보았다.

let testRelay = BehaviorRelay(value: 1)

_ = Observable.zip(
            testRelay.debug("중간결과1"),  
            Observable.zip(Observable.just(2), Observable.just(3)) {    
                return $0 + $1
            }.debug("중간결과2")
        ) {
            return $0 + $1
        }.debug("결과값")
        .subscribe()
결과값 -> subscribed
중간결과1 -> subscribed
중간결과1 -> Event next(1)
중간결과2 -> subscribed
중간결과2 -> Event next(5)
결과값 -> Event next(6)
중간결과2 -> Event completed
중간결과2 -> isDisposed

이번에는 외부 Observable.zip의 첫번째 인자로 종료시점이 정해지지 않은 BehaviorRelay를 넣어줬다. 두번째 인자인 Observable.zip(중간결과2)이 complete 후 dispose됐지만 첫번째 인자는 dispose되지 않았고, 따라서 외부 Observable.zip도 dispose되지 않았다. 즉, Observable.zip은 subscribe한 내부 Observable 인자들이 모두 종료된 경우에만 disposed된다.

 

그렇다면 종료되지 않은 하나의 Observable 인자에서 계속 데이터를 내보낸다면??

@IBOutlet weak var randomButton: UIButton!
let testRelay = BehaviorRelay(value: 1)

				_ = randomButton.rx.tap
            .subscribe(onNext: { [weak self] in
                self?.testRelay.accept(Int.random(in: 0...100))
            })
        
        _ = Observable.zip(
            testRelay.debug("중간결과1"),  
            Observable.zip(Observable.just(2), Observable.just(3)) {    
                return $0 + $1
            }.debug("중간결과2")
        ) {
            return $0 + $1
        }.debug("결과값")
        .subscribe()

버튼을 하나 만들고, 버튼을 누를 때마다 testRelay에 랜덤한 값을 넘겨주었다. 결과는 아래와 같다.

결과값 -> subscribed
중간결과1 -> subscribed
중간결과1 -> Event next(1)
중간결과2 -> subscribed
중간결과2 -> Event next(5)
결과값 -> Event next(6)
중간결과2 -> Event completed
중간결과2 -> isDisposed
//버튼을 세번 누른 뒤
중간결과1 -> Event next(52)
중간결과1 -> Event next(90)
중간결과1 -> Event next(28)

결과를 보면 하나의 Observable인자가 이미 disposed 되었기 때문에 다른 인자에서 데이터를 보낸다고 하더라도 전체 Observable.zip은 동작되지 않는다.

 

Observable.zip 인자 하나가 에러를 내보낸다면??

let testRelay = BehaviorRelay(value: 1)
let testRelay2 = BehaviorRelay(value: 2)
let testRelay3 = BehaviorRelay(value: 3)

_ = Observable.zip(
            testRelay.debug("testRelay1"),
            Observable.zip(testRelay2.debug("testRelay2"), testRelay3.debug("testRelay3")) { _, _ in
                return Observable<Int>.error(TestError.fakeError)
            }
            .flatMap { $0 }
            .debug("중간결과")
        ) {
            return $0 + $1
        }.debug("결과값")
        .subscribe()

결과는 아래와 같다.

결과값 -> subscribed
testRelay1 -> subscribed
testRelay1 -> Event next(1)
중간결과 -> subscribed
testRelay2 -> subscribed
testRelay2 -> Event next(2)
testRelay3 -> subscribed
testRelay3 -> Event next(3)
중간결과 -> Event error(fakeError)
결과값 -> Event error(fakeError)
Unhandled error happened: fakeError
결과값 -> isDisposed
중간결과 -> isDisposed
testRelay2 -> isDisposed
testRelay3 -> isDisposed
testRelay1 -> isDisposed

flatMap과 같이 Observable.zip에서도 특정 Observable 인자가 error를 내보내면 전체 시퀀스에도 영향을 미치는 것으로 보인다.

 

error와 관련하여 하나 더 테스트 해보았다.

let testRelay = BehaviorRelay(value: 1)
let testRelay2 = BehaviorRelay(value: 2)
let testRelay3 = BehaviorRelay(value: 3)

_ = Observable.zip(
            testRelay.debug("testRelay1"),
            Observable.zip(testRelay2.debug("testRelay2"), testRelay3.debug("testRelay3")) { _, _ in
                return Observable<Int>.error(TestError.fakeError)
            }
            .flatMap { $0 }
            .debug("중간결과")
        ) {
            return $0 + $1
        }.debug("결과값1")
        .do(onError: { error in
            print("중간에 있는 do onError: \(error)")
        })
        .flatMap {
            return Observable.just($0*2)
        }.debug("결과값2")
        .subscribe(onNext: { number in
            print("onNext: \(number)")
        }, onError: { error in
            print("onError: \(error)")
        })
				

결과는 아래와 같았다.

결과값2 -> subscribed
결과값1 -> subscribed
testRelay1 -> subscribed
testRelay1 -> Event next(1)
중간결과 -> subscribed
testRelay2 -> subscribed
testRelay2 -> Event next(2)
testRelay3 -> subscribed
testRelay3 -> Event next(3)
중간결과 -> Event error(fakeError)
결과값1 -> Event error(fakeError)
중간에 있는 do onError: fakeError
결과값2 -> Event error(fakeError)
onError: fakeError
결과값2 -> isDisposed
결과값1 -> isDisposed
중간결과 -> isDisposed
testRelay2 -> isDisposed
testRelay3 -> isDisposed
testRelay1 -> isDisposed

중간에 error가 발생하는 경우 이 error가 subscribe부분까지 쭉 이어져 내려온다. (이후의 flatMap 변환은 온전히 작동하지 않는다.) 따라서 ‘중간에 error가 나는 경우 에 대해 마지막 subscribe에서 처리하는 것’이 가능하다. (중간에 나는 error는 해당 시퀀스부분의 do에서 처리해야 하나 해서 do구문을 넣어주었는데 위 결론에 따르면 중간에 do 구문은 안넣어도 됨)

 

외부 Observable.zip에 take(1)를 써서 한번 실행 후 종료한다면 내부 Observable 인자들은 어떻게 될까?

let testRelay = BehaviorRelay(value: 1)
let testRelay2 = BehaviorRelay(value: 2)
let testRelay3 = BehaviorRelay(value: 3)
		
_ = Observable.zip(
            testRelay.debug("중간결과1"),  
            Observable.zip(testRelay2, testRelay3) {    
                return $0 + $1
            }.debug("중간결과2")
        ) {
            return $0 + $1
        }.debug("결과값")
        .take(1)
        .subscribe()

결과는 아래와 같았다.

결과값 -> subscribed
중간결과1 -> subscribed
중간결과1 -> Event next(1)
중간결과2 -> subscribed
중간결과2 -> Event next(5)
결과값 -> Event next(6)
결과값 -> isDisposed
중간결과1 -> isDisposed
중간결과2 -> isDisposed

즉, 외부에서 시퀀스가 종료됨에 따라 내부 인자 시퀀스들도 종료가 되었다.

정리하자면 Observable.zip은 내부 Observable인자들에 대해 subscribe를 진행하는데, 모든 Observable 들이 종료되면 zip도 같이 종료되며, 하나라도 살아있으면 종료되지 않는다. 반대로 Observable.zip 자체가 종료되는 경우 인자로 있는 모든 Observable들에 대한 subscribe가 종료된다.

카테고리:

업데이트:

댓글남기기