brunch
매거진 ReactiveX

Disposable 의 종류

Boolean, Serial, Composite,RefCount...

by Tilltue

* 이 포스트는 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 프로토콜을 따른다. 이후에 추가될지? 모르겠다.


keyword
매거진의 이전글여러 옵저버블을 한 옵저버로 바인드 가능하다.