Disposables

๐Ÿ—‘๏ธ Disposables โ€” ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์—†๋Š” RxSwift ์‚ฌ์šฉ๋ฒ•

โ€œ๊ตฌ๋…์€ ๊ณง ๋ฆฌ์†Œ์Šค.โ€ ํ•ด์ง€(Dispose)ํ•˜์ง€ ์•Š์œผ๋ฉด ObserverยทObservableยทClosure ๋ชจ๋‘ ๋ฉ”๋ชจ๋ฆฌ์— ๋‚จ์Šต๋‹ˆ๋‹ค.

์ด ์žฅ์—์„œ๋Š” Disposable ํ”„๋กœํ† ์ฝœ๊ณผ DisposeBag, ๊ทธ๋ฆฌ๊ณ  ์ž๋™ ํ•ด์ œ ์˜คํผ๋ ˆ์ดํ„ฐ๋ฅผ ํ†ตํ•ด ๋ˆ„์ˆ˜ ์—†๋Š” Rx ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค.


1๏ธโƒฃ Disposable ๊ธฐ๋ณธ ๊ฐœ๋…

public protocol Disposable {
    func dispose()
}
  • ์—ญํ• : Observable ๊ตฌ๋…์„ ์ทจ์†Œํ•˜๊ณ  ๊ด€๋ จ ๋ฆฌ์†Œ์Šค๋ฅผ ํ•ด์ œ.

  • ์ƒ์„ฑ ๊ฒฝ๋กœ: subscribe() ๋ฉ”์„œ๋“œ๋Š” ํ•ญ์ƒ Disposable์„ ๋ฐ˜ํ™˜.

let disposable = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
    .subscribe(onNext: { print($0) })

// 5์ดˆ ํ›„ ์ˆ˜๋™ ํ•ด์ œ
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    disposable.dispose()
}

์ฃผ์˜: ์ˆ˜๋™ dispose() ํ˜ธ์ถœ์„ ์žŠ์œผ๋ฉด Hot Observable๊ณผ Timer ๋“ฑ์ด ์˜์›ํžˆ ์‚ด์•„์žˆ์–ด ๋ˆ„์ˆ˜ยท๋ฐฐํ„ฐ๋ฆฌ ์†Œ๋ชจ ๊ฐ€๋Šฅ.


2๏ธโƒฃ DisposeBag โ€” ์ˆ˜์ง‘ ํ›„ ์ผ๊ด„ dispose

ํŠน์ง•
์„ค๋ช…

์Šค์ฝ”ํ”„ ๋‹จ์œ„ ๊ด€๋ฆฌ

Bag์ด deinited ๋˜๋ฉด ๋‚ด๋ถ€ ๋ชจ๋“  Disposable ์ž๋™ ํ•ด์ œ

์ผ๋ฐ˜ ํŒจํ„ด

ViewControllerยทViewModel์— let bag = DisposeBag() ์ƒ์„ฑ

ARC ํ™œ์šฉ

ํด๋กœ์ € retain cycle ์—†์ด ์ƒ๋ช…์ฃผ๊ธฐ ์ผ์น˜

class LoginViewModel {
    private let bag = DisposeBag()

    func bind(input: Input) {
        input.loginTap
            .flatMapLatest(api.login)
            .subscribe()
            .disposed(by: bag) // โœ…
    }
}

3๏ธโƒฃ ๊ธฐํƒ€ Disposable ๊ตฌํ˜„์ฒด

๊ตฌํ˜„์ฒด
์šฉ๋„

SerialDisposable

๋‚ด๋ถ€ Disposable์„ ๊ต์ฒด ๊ฐ€๋Šฅ (์˜ˆ: ์žฌ์‹œ๋„ ์‹œ ์ด์ „ ์š”์ฒญ ์ทจ์†Œ)

CompositeDisposable

์—ฌ๋Ÿฌ Disposable์„ ๋ฌถ์–ด ์ผ๊ด„ ํ•ด์ œ (DisposeBag Swift ๋ฒ„์ „)

RefCountDisposable

์ฐธ์กฐ ์นด์šดํŠธ ๊ธฐ๋ฐ˜, ๋งˆ์ง€๋ง‰ dispose ๋•Œ ์‹ค์ œ ํ•ด์ œ

let serial = SerialDisposable()

searchQuery
    .flatMapLatest(api.search) // ์ƒˆ ๊ฒ€์ƒ‰๋งˆ๋‹ค ์ด์ „ ์š”์ฒญ out
    .subscribe()
    .disposed(by: serial)

4๏ธโƒฃ ์ž๋™ ํ•ด์ œ ์˜คํผ๋ ˆ์ดํ„ฐ

์˜คํผ๋ ˆ์ดํ„ฐ
์„ค๋ช…
์˜ˆ์‹œ

take(_:)

n๊ฐœ ์š”์†Œ ํ›„ completed

๋ฒ„ํŠผ ์ฒซ 1ํšŒ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ

takeUntil(_:)

Trigger Observable emit ์‹œ completed

ViewWillDisappear์— ์—ฐ๋™

timeout(_:)

๊ธฐํ•œ ๋‚ด ์ด๋ฒคํŠธ ์—†์œผ๋ฉด error

์„œ๋ฒ„ ์‘๋‹ต ๋Œ€๊ธฐ

single()

์ฒซ ์š”์†Œ + completed

๊ฒฐ๊ณผ 1๊ฐœ ๋ณด์žฅ API

viewWillDisappearSignal
    .subscribe(onNext: { _ in print("Bye") })
    .disposed(by: bag)

observable
    .takeUntil(viewWillDisappearSignal)
    .subscribe()
    .disposed(by: bag) // ์ž๋™ ์ข…๋ฃŒ

5๏ธโƒฃ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ์ฒดํฌ ํŒ

  1. Debug Memory Graph(โ‡งโŒ˜I) ํ›„ ViewController ์ธ์Šคํ„ด์Šค ํ™•์ธ.

  2. Xcode Allocations Instrument์—์„œ Live Count ์ถ”์ .

  3. RxSwiftCommunity์˜ LeakDetector ์‚ฌ์šฉ (rx.disableLeakDetection = false).


6๏ธโƒฃ Best Practices Checklist โœ…


7๏ธโƒฃ Mini Quiz

  1. DisposeBag ๋Œ€์‹  SerialDisposable์ด ๋” ์ ํ•ฉํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋Š”?

  2. take(1)๊ณผ single()์˜ ๋™์ž‘ ์ฐจ์ด ํ•œ ์ค„ ์š”์•ฝ.

  3. ViewController deinit์ด ํ˜ธ์ถœ๋˜์ง€ ์•Š์„ ๋•Œ ํ™•์ธํ•ด์•ผ ํ•  ์„ธ ๊ฐ€์ง€ ํ•ญ๋ชฉ์€?

์ •๋‹ต
  1. ์‹œ๋‚˜๋ฆฌ์˜ค: ์‚ฌ์šฉ์ž๊ฐ€ ๊ฒ€์ƒ‰์–ด๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ฐ”๊ฟ€ ๋•Œ ์ด์ „ ๋„คํŠธ์›Œํฌ ์š”์ฒญ์„ ์ทจ์†Œํ•˜๊ณ  ์ตœ์‹  ์š”์ฒญ๋งŒ ์œ ์ง€ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ; SerialDisposable().swap(_:)์œผ๋กœ ์ด์ „ ๊ตฌ๋… dispose.

  2. take(1)์€ ์ฒซ next ์ด๋ฒคํŠธ ํ›„ completed, single()์€ ์ฒซ next ์ด๋ฒคํŠธ ํ›„ completed ๋˜๋Š” error(์š”์†Œ >1 ๋˜๋Š” 0๊ฐœ).

  3. ํ™•์ธ ํ•ญ๋ชฉ

  • ํด๋กœ์ €ยทObservable์—์„œ self๋ฅผ strong ์บก์ฒ˜ ํ–ˆ๋Š”์ง€ ([weak self]).

  • DelegateยทNotification ๋“ฑ ์ „ํ†ต์  ๋ฆฌํ…Œ์ธ ์‚ฌ์ดํด.

  • DisposeBag์ด VC ํ”„๋กœํผํ‹ฐ๊ฐ€ ์•„๋‹Œ ์ „์—ญยท์‹ฑ๊ธ€ํ„ด์— ํฌํ•จ๋˜์—ˆ๋Š”์ง€.

Last updated