Swift Extension Cheat Sheet

๐Ÿ“ RxSwift Extension Cheat Sheet โ€” ์‹ค๋ฌด์— ๋ฐ”๋กœ ์“ฐ๋Š” ์œ ํ‹ธ ๋ชจ์Œ

โ€œRx ์˜คํผ๋ ˆ์ดํ„ฐ๊ฐ€ ๋ถ€์กฑํ•  ๋•Œ, ์šฐ๋ฆฌ๋งŒ์˜ ํ•œ ์ค„์„ ๋”!โ€

์•„๋ž˜ ํ™•์žฅ๋“ค์€ ๋ชจ๋‘ Observable / Single / Driver / ControlEvent ๋“ฑ Rx ํƒ€์ž…์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. ์ˆœ์ˆ˜ Swift ์œ ํ‹ธ(๋ฌธ์ž์—ด, ๋ฐฐ์—ด ๋“ฑ)์€ ์ œ์™ธํ–ˆ์œผ๋‹ˆ, Rx ํ๋ฆ„์—์„œ ๋ฐ”๋กœ ๋ณต์‚ฌยท๋ถ™์—ฌ๋„ฃ๊ณ  ์‚ฌ์šฉํ•ด ๋ณด์„ธ์š”.


1๏ธโƒฃ Observable โŸถ Driver/Signal ๋ณ€ํ™˜

import RxSwift
import RxCocoa

extension ObservableType {
    /// ์—๋Ÿฌ๋ฅผ ๋ฌด์‹œํ•˜๊ณ  Completed ์ฒ˜๋ฆฌํ•˜์—ฌ Driver๋กœ ๋ณ€ํ™˜
    func asDriverOnErrorJustComplete() -> Driver<Element> {
        asDriver { _ in .empty() }
    }

    /// ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋Œ€์ฒดํ•œ ๋’ค Driver ๋ฐ˜ํ™˜
    func asDriver(onErrorJustReturn value: Element) -> Driver<Element> {
        asDriver { _ in .just(value) }
    }
}
๋ฉ”์„œ๋“œ
์‚ฌ์šฉ ์˜ˆ

asDriverOnErrorJustComplete()

ViewModel Output โ†’ UI ๋ฐ”์ธ๋”ฉ ์‹œ ์˜ค๋ฅ˜ ๋ฌด์‹œ

asDriver(onErrorJustReturn:)

๋„คํŠธ์›Œํฌ ์‹คํŒจ ์‹œ Placeholder ๋ฐ์ดํ„ฐ ์ „๋‹ฌ


2๏ธโƒฃ map / filter ๊ณ„์—ด Sugar

extension ObservableType {
    /// ๋ชจ๋“  ์š”์†Œ๋ฅผ Void๋กœ ๋ณ€ํ™˜ (๊ฐ’์ด ํ•„์š” ์—†์„ ๋•Œ)
    func mapToVoid() -> Observable<Void> { map { _ in } }
}

extension Observable where Element: OptionalType {
    /// nil ์„ ํ•„ํ„ฐ๋งํ•˜๊ณ  ๋ž˜ํ•‘์„ ๋ฒ—๊ฒจ๋‚ธ๋‹ค (Optional ์ œ๊ฑฐ)
    func filterNil() -> Observable<Element.Wrapped> {
        compactMap { $0.asOptional }
    }
}

OptionalType ํ”„๋กœํ† ์ฝœ์€ associatedtype Wrapped ์™€ var asOptional: Wrapped? ๋กœ ์ •์˜ ํ›„ ์‚ฌ์šฉ.


3๏ธโƒฃ ControlEvent ํŽธ์˜

extension ControlEvent where Element == Void {
    /// ๋ฒ„ํŠผ ์—ฐํƒ€ ๋ฐฉ์ง€: ๊ธฐ๋ณธ 300ms ์“ฐ๋กœํ‹€
    func throttleTap(_ interval: RxTimeInterval = .milliseconds(300)) -> ControlEvent<Void> {
        throttle(interval, scheduler: MainScheduler.instance)
    }
}
  • button.rx.tap.throttleTap() ์œผ๋กœ ๊ฐ„๋‹จ ์ ์šฉ


4๏ธโƒฃ Subject / Relay Helper

import RxRelay

extension PublishRelay {
    /// ํ˜„์žฌ ์“ฐ๋ ˆ๋“œ์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ onNext (Main ์—ฌ๋ถ€ ์„ ํƒ)
    func acceptOnMain(_ element: Element) {
        if Thread.isMainThread {
            accept(element)
        } else {
            DispatchQueue.main.async { self.accept(element) }
        }
    }
}

5๏ธโƒฃ Disposable & DisposeBag

extension Disposable {
    /// DisposeBag ์ด ์—†๋Š” ์งง์€ ์Šค์ฝ”ํ”„์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ Auto-dispose
    func disposed(by bag: inout [Disposable]) {
        bag.append(self)
    }
}
var tempBag: [Disposable] = []
observable.subscribe().disposed(by: &tempBag)

6๏ธโƒฃ withUnretained Sugar (iOS 13 ์ดํ•˜ ์ง€์›)

extension ObservableType {
    /// RxSwift 6 ์˜ withUnretained ๋ฅผ iOS 12 ํ”„๋กœ์ ํŠธ์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•˜๊ฒŒ shim.
    func withUnretained<T: AnyObject>(_ owner: T) -> Observable<(T, Element)> {
        map { [weak owner] element in
            guard let owner = owner else { throw RxError.noElements }
            return (owner, element)
        }
        .catchError { _ in .empty() }
    }
}

7๏ธโƒฃ Mini Quiz (Selfโ€‘Check)

  1. mapToVoid() ์™€ ignoreElements() ์˜ ์ฐจ์ด๋Š”?

  2. Relay ์—์„œ acceptOnMain ์ด ํ•„์š”ํ•œ ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ?

  3. asDriver(onErrorJustReturn:) ์‚ฌ์šฉ ์‹œ ๋ฌดํ•œ ๋ฃจํ”„๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋Š” ์‹œ๋‚˜๋ฆฌ์˜ค๋Š”?

Answers
  1. mapToVoid() ๋Š” ๊ฐ onNext ๊ฐ’์„ Void๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ด๋ฒคํŠธ๋ฅผ ๊ทธ๋Œ€๋กœ ์œ ์ง€, ignoreElements() ๋Š” onNext ์ž์ฒด๋ฅผ ๋ฌด์‹œํ•˜๊ณ  Completion/Error ๋งŒ ์ „๋‹ฌ.

  2. ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ accept ํ˜ธ์ถœ ์‹œ UI Relay ๊ฐ€ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์ œ์•ฝ์„ ์–ด๊ธธ ์œ„ํ—˜, ์Šค๋ ˆ๋“œ ์•ˆ์ „ ํ™•๋ณด.

  3. ๊ธฐ๋ณธ๊ฐ’์„ ๋‹ค์‹œ ์—ฐ์‚ฐ์— ์‚ฌ์šฉํ•ด ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐ˜๋ณต โ†’ retry ์™€ ๊ฒฐํ•ฉ ์‹œ ์ฃผ์˜.


๋ชจ๋“  ํ™•์žฅ์€ Rx ์ค‘์‹ฌ ์œผ๋กœ ์žฌ๊ตฌ์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. ํ•„์š”์— ๋”ฐ๋ผ ํšŒ์‚ฌ/ํ”„๋กœ์ ํŠธ์— ๋งž๊ฒŒ ์ด๋ฆ„ยท๋„ค์ž„์ŠคํŽ˜์ด์Šค๋ฅผ ์กฐ์ •ํ•ด ํ™œ์šฉํ•ด ๋ณด์„ธ์š”. ๐Ÿš€

Last updated