Boolean, Serial, Composite,RefCount...
* 이 포스트는 RxSwift 4.3.1, swift 4.2 버전을 기준으로 작성되었습니다.
브런치에서는 코드보기가 힘들어서 아래의 예제 코드들을 gist 에 정리했다.
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)
}
}
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 로 되어있다.
여러 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
참조 카운트를 가진 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
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} // 메인 스레드
하나의 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
...
하나의 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 프로토콜을 따른다. 이후에 추가될지? 모르겠다.