brunch

매거진 ReactiveX

You can make anything
by writing

C.S.Lewis

by Tilltue Jul 20. 2018

Disposable 의 종류

Boolean, Serial, Composite,RefCount...

* 이 포스트는 RxSwift 4.3.1, swift 4.2 버전을 기준으로 작성되었습니다.


브런치에서는 코드보기가 힘들어서 아래의 예제 코드들을 gist 에 정리했다.



1. Disposables ( noOp, op )

NopDisposable, AnonymousDisposable


- Disposables.create()

Observable dispose시 특별한 처리가 필요하지 않을때 사용된다.


예제

Observable<String>.create { (observer) -> Disposable in

            observer.on(.next("observable start"))

            observer.on(.next("observable end"))

            return Disposables.create()

        }

문자열을 두번 출력하며 dispose 될때 아무런 처리가 필요하지 않은 경우이다.


Disposables.create() 내용을 보면 아래와 같다.

extension Disposables {

    static public func create() -> Disposable {

        return NopDisposable.noOp 

    }

}

dispose될때 처리를 해주어야 할 동작이 없을때 ( noOp ) 사용되는 것이라고 이해하자.


- Disposables.create{  /* op */ }

dispose 시 처리할 동작이 있는 경우에 사용된다.


예제- ( RxSwift githhub README 에서 발췌한 타이머 Observable)


 let timer = Observable<Int>.create { observer in

            let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())

            timer.schedule(deadline: DispatchTime.now() + 1, repeating: 1)

            let cancel = Disposables.create {

                print("Disposed")

                timer.cancel()

            }

            var next = 0

            timer.setEventHandler {

                if cancel.isDisposed {

                    return

                }

                observer.on(.next(next))

                next += 1

            }

            timer.resume()

            return cancel

}

위의 코드에서 타이머는 dispose 될때 timer 를 취소하며 isDisposed 값에 따라 이벤트를 발생시키지 않도록 되어있다.


이경우 Disposables.create{ /* op */ } 내용을 보면 아래와 같다.

extension Disposables {

    public static func create(with dispose: @escaping () -> ()) -> Cancelable {

        return AnonymousDisposable(disposeAction: dispose)

    }

}


2. BooleanDisposable

dispose 된 상태여부만 알고싶은 경우를 위한 타입이다.


비동기 작업을 수행하는 Observable 을 만들었다고 하자.

더이상 그 작업이 필요하지 않게 되어 Observable 을 dispose 시킨뒤 이후 비동기 작업의 결과를 수행하지 않기 위해 상태값이 필요할수 있다.

이처럼 dispose 시 처리가 필요하진 않지만  dispose 상태를 알아야 할때 사용하면 된다.


주 : isDisposed는 Cancelable 프로토콜의 property 이다.

대부분의 disposable 은 cancelable 프로토콜을 따른다.


Observables.create{ (code) } : Cancelable 프로토콜을 따른다.

fileprivate final class AnonymousDisposable : DisposeBase, Cancelable

BooleanDisposable : Cancelable 프로토콜을 따른다.

public final class BooleanDisposable : Cancelable


예제

       let task = Observable<String>.create { observer in

            let cancelDisposable  =  BooleanDisposable()

            let queue = OperationQueue()

            func addOperation(delay: UInt32) {

                queue.addOperation {

                    sleep(delay)

                    print(cancelDisposable.isDisposed)

                    if cancelDisposable.isDisposed {

                        queue.cancelAllOperations()

                    }

                    observer.on(.next("Do my job!!"))

                }

            }

            addOperation(delay: 1)

            addOperation(delay: 2)

            addOperation(delay: 3)

            addOperation(delay: 4)

            addOperation(delay: 5)

            return cancelDisposable

     }

     task.take(3).subscribe(onNext: {

            print($0)

     }).disposed(by: disposeBag)


결과

false

Do my job!!

false

Do my job!!

false

Do my job!!

true

true


위의 결과처럼 3번째 이후 dispose 되었으므로 isDisposed 값이 true 로 되어있다.


3. CompositeDisposable

여러 disposable 을 묶어서 처리할수 있다.

disposable insert시 제공하는 key 값을 사용해 단독으로 dispose 할수도 있다.


CompositeDisposable은 DisposeBase 프로토콜을 따르고 있다,

public final class CompositeDisposable : DisposeBase, Cancelable


DisposeBag 또한 DisposeBase 프로토콜을 따르고 있다.

public final class DisposeBag: DisposeBase


주: CompositeDisposeBag을 사용할때 컴파일 시간이 늘어난걸 겪었던 경험이 있다. 수치상으로는 꽤 차이가 날수있으니, 빌드타임에 민감한 프로젝트라면 사용시 한번씩 체크해 보는것도 좋을것 같다.


예제

         let rabbit = Observable<Int>.interval(2, scheduler: MainScheduler.instance)

                              .subscribe(onNext: {

                                      print("rabbit: \($0)")

                                })   

        let fox = Observable<Int>.interval(3, scheduler: MainScheduler.instance)

                        .subscribe(onNext: {

                                print("fox: \($0)")

                         })

        let snake = Observable<Int>.interval(5, scheduler: MainScheduler.instance)

                            .subscribe(onNext: {

                                    print("snake: \($0)")

                            })

        let compositeDisposable = CompositeDisposable.init(disposables: [rabbit])

        let key = compositeDisposable.insert(fox) //insert 를 사용하면 키를 가져올수 있다.

        let _ = compositeDisposable.insert(snake)

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) {

            print("dispose fox")

            if let key = key {

                compositeDisposable.remove(for: key)

            }

        }

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+6) {

            print("dispose")

            compositeDisposable.dispose()

        }       

설명

key 를 통해 3초뒤 snake 이벤트를 dispose 시키고 6초 뒤에는 compositdisposable 에 묶여있는 disposable 들을 모두 dispose 한다.


결과

rabbit: 0 

rabbit: 1 

fox: 0 

rabbit: 2 

dispose fox 

rabbit: 3 

snake: 0 

rabbit: 4 

rabbit: 5 

dispose


4. RefCountDisposable

참조 카운트를 가진 disposable 객체를 사용할수 있다.

어떤때 사용해야 유용할까? 고민 해봤지만 잘 생각나지는 않는다.


예제

         let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance)

                              .subscribe(onNext: {

                                    print("timer: \($0)")

                                })

        let refCountDisposable = RefCountDisposable(disposable: timer)

        let retainDisposable = refCountDisposable.retain()

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) {

            print("dispose")

            refCountDisposable.dispose()

        }

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+6) {

            print("dispose")

            retainDisposable.dispose()

        }

설명

refcountdisposable 에 retain()을 통해 참조를 가진 disposable 을 만들었다.

이 disposable 이 dispose 될때까지 이벤트가 발생하는 것을 볼수 있다.

retain() 을 통해 여러개의 참조를 가진 disposable 들을 생성할수도 있다.


결과

timer: 0 

timer: 1 

timer: 2 

dispose 

timer: 3 

timer: 4 

timer: 5 

dispose


5. ScheduleDisposable

dispose 될 스케쥴러를 지정할 수 있다.


예제

let task = Observable<String>.create { observer in

            let disposable = Disposables.create {

                print("dispose: \(Thread.current)")

            }

            let queue = OperationQueue()

            func addOperation(delay: UInt32) {

                queue.addOperation {

                    sleep(delay)

                    if disposable.isDisposed {

                        queue.cancelAllOperations()

                    }

                    observer.on(.next("Do my job!!"))

                }

            }

            addOperation(delay: 1)

            addOperation(delay: 2)

            return disposable

        }.subscribe(onNext: {

            print($0)

        })

        //let scheduleDisposable = ScheduledDisposable(scheduler: ConcurrentDispatchQueueScheduler(qos: .background), disposable: task)  // - 1번

        let scheduleDisposable = ScheduledDisposable(scheduler: MainScheduler.instance, disposable: task) // - 2번

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) {

            print("dispose")

            scheduleDisposable.dispose()

        }


결과


1번 결과

Do my job!! 

Do my job!! 

dispose 

dispose: <NSThread: 0x608000270600>{number = 6, name = (null)} // 백그라운드 스레드


2번 결과

Do my job!! 

Do my job!! 

dispose 

dispose: <NSThread: 0x608000078200>{number = 1, name = main} // 메인 스레드


6. SerialDisposable

하나의 disposable 을 가지며, 새로운 disposable 이 설정될때 이전에 등록된 disposable 을 dispose 시킨다.


예제

func serialDisposableSample() {

        let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance).subscribe(onNext: {

            print("timer: \($0)")

        })

        let serialDisposable = SerialDisposable()

        serialDisposable.disposable = timer

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) {

            let timer2 = Observable<Int>.interval(1, scheduler: MainScheduler.instance).subscribe(onNext: {

                print("timer2: \($0)")

            })

            serialDisposable.disposable = timer2

        }

    }

설명 3초뒤 새로운 disposable 이 등록될때 dispose 된것을 알수 있다.


결과

timer: 0 

timer: 1 

timer: 2 

// dispose timer

timer2: 0 

timer2: 1 

...


6. SingleAssignmentDisposable

하나의 disposable 만을 처리할수 있도록 한다. 만약 다른 disposable 을 다시 할당하려고 하면 에러를 발생시킨다. 에러를 발생시키므로 사용에 유의해야 한다.


func singleAssignmentDisposableSample() {

        let timer = Observable<Int>.interval(1, scheduler: MainScheduler.instance).subscribe(onNext: {

            print("timer: \($0)")

        })

        let singleAssignmentDisposable = SingleAssignmentDisposable()

        singleAssignmentDisposable.setDisposable(timer)

        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) {

            let timer2 = Observable<Int>.interval(1, scheduler: MainScheduler.instance).subscribe(onNext: {

                print("timer2: \($0)")

            })

            singleAssignmentDisposable.setDisposable(timer2) //에러 발생

        }

    }



여기까지 RxSwift 의 disposable 종류들에 대해 정리해보았다.

Disposable 의 종류에 대해 무엇무엇이 있는지 알고만 있으면 될것 같다. 필요한 상황이 오면 떠올릴수 있을정도로만.


생략한 것:

BinaryDisposable 은 private 로 되어있어서 정리하진 않았다. 코드상으로는 2개의 dispose 를 묶어서 사용하고 싶을때 사용하는 것으로 보인다. DisposeBase 프로토콜을 따른다. 이후에 추가될지? 모르겠다.


브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari