๊ธ€ ์ž‘์„ฑ์ž: ์ด์ง€์›๐ŸŒฉ๏ธ

์˜ต์ €๋ฒ„ ํŒจํ„ด

  • 1:N ๊ด€๊ณ„๋กœ ์ด๋ฃจ์–ด์ง„, ๊ด€์ฐฐ ํŒจํ„ด
  • ๊ฐ์ฒด๋ฅผ ๊ตฌ๋…ํ•˜๋ฉด ์˜ต์ €๋ฒ„๊ฐ€ ์ƒํƒœ๋ฅผ ์•Œ๋ ค์คŒ


ํ•จ์ˆ˜ํ˜• ํ”„๋กœ๊ทธ๋ž˜๋ฐ

  • 1๊ธ‰ ๊ฐ์ฒด
    • ๋ณ€์ˆ˜๋‚˜ ์ƒ์ˆ˜์— ์ €์žฅ์„ ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•จ
    • ํ•จ์ˆ˜์—์„œ return ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•จ
    • ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•จ
  • ์ˆœ์ˆ˜ ํ•จ์ˆ˜
  • /* ํ™˜์œจ ๊ณ„์‚ฐํ•ด์ฃผ๋Š” ํ”„๋กœ๊ทธ๋žจ ์ด ์ฝ”๋“œ๋Š” ์ˆœ์ˆ˜ ํ•จ์ˆ˜๋ผ๊ณ  ํ•  ์ˆ˜ ์—†๋‹ค. → ์ „์—ญ ์ƒํƒœ์— ์˜์กด = ์ฐธ์กฐ ํˆฌ๋ช…์„ฑ์ด ์—†๋‹ค */ 
    var rate = 1120 
    func krw(usd: Int) -> Int { 
    	return usd * rate 
    } 
    
    krw(usd: 2) // 2240 
    krw(usd: 3) // 3360 
    rate = 1130 
    krw(usd: 2) // 2260 
    krw(usd: 3) // 3390


ReactiveX

  • ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•œ ์ŠคํŠธ๋ฆผ์„ ์ด์šฉํ•œ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ API
  • Reactive eXtension


RxSwift

  • Observable
    • Rx์˜ ๊ธฐ๋ณธ์ด ๋˜๋Š” ์ŠคํŠธ๋ฆผ์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๊ธฐ๋ณธ ์‹œํ€€์Šค
    • ํ•˜๋‚˜ ๋˜๋Š” ์—ฐ์†๋œ ํ•ญ๋ชฉ์„ ๋ฐฐ์ถœ
    • ๊ตฌ๋… ํ›„ next, error, completed๋กœ ์ฒ˜๋ฆฌ
      • next ์ŠคํŠธ๋ฆผ
        • ์—ฐ์†๋œ ๊ฐ’๋“ค์„ ๋ฐฐ์ถœํ•˜๊ณ  ์˜ต์ €๋ฒ„๋Š” next ์ŠคํŠธ๋ฆผ์„ ๊ด€์ฐฐ ๋ฐ ๊ตฌ๋…ํ•ด์„œ ์›ํ•˜๋Š” ํ–‰๋™์„ ํ•จ
      • error ์ŠคํŠธ๋ฆผ
        • ๊ฐ’์„ ๋ฐฐ์ถœํ•˜๋‹ค๊ฐ€ ์—๋Ÿฌ๊ฐ€ ์ƒ๊ธฐ๋ฉด error๋ฅผ ๋ฐฐ์ถœํ•œ ๋‹ค์Œ ํ•ด๋‹น Observable์€ ์ŠคํŠธ๋ฆผ์„ ๋ฉˆ์ถค
      • complete
        • Observable์ด ๋ชจ๋“  ๊ฐ’์„ ๋‹ค ๋ฐฐ์ถœํ•˜๋ฉด ์ด ์ƒํƒœ(?)๊ฐ€ ๋จ
        • error๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด complete์€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ!
  • Operator
    • Observable ์•ˆ์—์„œ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ชจ๋“  ์—ฐ์‚ฐ๋“ค
    • map, filter, merge ๋“ฑ ์ˆ˜์‹ญ๊ฐ€์ง€์˜ ์—ฐ์‚ฐ์ด ์žˆ์Œ
      • ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹์œผ๋กœ ๋งŽ์ด ์ง„ํ–‰ํ•จ
  • Single
    • Observable ๊ฐ™์ด N๋ฒˆ์ด ์•„๋‹Œ, ๋‹จ๋ฒˆ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•œ ์‹œํ€€์Šค
    • ๊ตฌ๋… ํ›„ success, error ๋‘ ๊ฐ€์ง€๋กœ ์ฒ˜๋ฆฌ
    • ํ•œ๋ฒˆ๋งŒ ์˜ค๊ณ , ํ•œ๋ฒˆ์— ๋๋‚จ
  • Subject
    • ์—ฌ๋Ÿฌ๊ฐœ์˜ Observer๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€์ฐฐํ•ด์•ผ ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ์ฒด
    • Observer ์™€ Observable ์‚ฌ์ด์˜ ๋ธŒ๋ฆฟ์ง€ ์—ญํ• 
    • ์—ฌ๋Ÿฌ ๊ฐœ์˜ Observer๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€์ฐฐํ•ด์•ผํ•˜๋Š” ๊ฒฝ์šฐ ์ž์ฃผ ์“ฐ์ž„


๊ตณ์ด?

  • ์™œ ๋น„์šฉ์„ ๋“ค์—ฌ๊ฐ€๋ฉด์„œ?
  • ์™œ ์ž˜ ๋Œ์•„๊ฐ€๋Š” ์ฝ”๋“œ๋ฅผ ๋ฐ”๊ฟ”๊ฐ€๋ฉด์„œ?
  • ์™œ ๊ผญ ๋” ๋‚œ์ด๋„ ์žˆ๋Š” RxSwift๋ฅผ?


์ด์œ 

  • ๊ฐ„๊ฒฐํ•ด์ง€๋Š” ์ฝ”๋“œ
    • ์กฐ๊ธˆ ๋” ๊ธธ์–ด์ง€๋”๋ผ๋„, ๊ฐ„๋‹จํ•˜๋ฉด์„œ๋„ ๊น”๋”ํ•ด์ง€๋Š” ์ฝ”๋“œ
  • ๊ฐ„ํŽธํ•œ ๋น„๋™๊ธฐ ๊ด€๋ฆฌ

 

RxSwift ์•Œ์•„๋ณด๊ธฐ 1 ~ 3 ๊ธฐ๋ก


์ •์ˆ˜ํ˜• ๋ฐฐ์—ด์˜ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ฒดํฌํ•˜๋Š” ํ•จ์ˆ˜ (RxSwift)

// items ๋ผ๋Š” ์ •์ˆ˜ ํƒ€์ž…์˜ ๋ฐฐ์—ด์„ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์Œ -> ์ •์ˆ˜ ์ œ๋„ค๋ฆญ ํƒ€์ž…์˜ Observable ๊ฐ์ฒด๋ฅผ ๋ฆฌํ„ด
func checkArrayObservable(items: [Int]) -> Observable<Int> {
    return Observable<Int>.create { observer -> Disposable in

        for item in items {
            if item == 0 {
                observer.onError(NSError(domain: "Error: value is zero.", code: 0, userInfo: nil)) // 0์ด๋ฉด ์—๋Ÿฌ๋ฅผ ํ˜๋ ค์ค€๋‹ค
                break
            }

            observer.onNext(item) // 0์ด ์•„๋‹ˆ๋ฉด ๊ฐ ์—˜๋ฆฌ๋จผํŠธ๋ฅผ next๋กœ ํ˜๋ ค์ค€๋‹ค

            sleep(1)
        }

        observer.onCompleted() // ๋ชจ๋“  ์ˆœํšŒ๊ฐ€ ๋๋‚˜๋ฉด completed ๋˜์—ˆ๋‹ค๋Š” ๊ฑธ ์•Œ๋ฆผ

        return Disposables.create()
    }
}


Subscribe, Dispose

Subscribe

Observable์˜ stream์„ ๊ด€์ฐฐํ•˜๊ณ  ๊ตฌ๋…ํ•ด์„œ ๋ฐ›๋Š” ์—ญํ• 

Disposable ์ด๋ผ๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค!

์˜ˆ์ œ ์ฝ”๋“œ

// interval: n์ดˆ๋งˆ๋‹ค ์ •์ˆ˜ ํƒ€์ž…์˜ ์ŠคํŠธ๋ฆผ์ด ๋ฐฉ์ถœ๋จ
Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
    .take(10) // parameter ๋งŒํผ์˜ ์ŠคํŠธ๋ฆผ ํ—ˆ์šฉ
    .subscribe(onNext: { value in
        print(value)
    }, onError: { error in
        print(error)
    }, onCompleted: {
        print("onCompleted")
    }, onDisposed: {
        print("onDisposed")
    })

// ์˜ˆ์™ธ ์ƒํ™ฉ ๋ฐœ์ƒ
// ์นด์šดํŒ…์ด ๋๋‚˜๊ธฐ ์ „์— ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ•ด์ œํ•ด ๋ฒ„๋ฆฐ๋‹ค๋ฉด?
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    UIApplication.shared.keyWindow?.rootViewController = nil
} // ์•ฑ์ด ์ฃฝ์—ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ๋๊นŒ์ง€ ์นด์šดํŠธ๊ฐ€ ์ง„ํ–‰๋จ

์ด๊ฒƒ์„ ํ•ด๊ฒฐํ•ด๋ณด์ž.

Disposable

disposable: ์ฒ˜๋ถ„ํ•  ์ˆ˜ ์žˆ๋Š”, ์‚ฌ์šฉ ํ›„ ๋ฒ„๋ฆด ์ˆ˜ ์žˆ๋Š”

public protocol Disposable {
    /// Dispose resource.
    func dispose()
}
// interval: n์ดˆ๋งˆ๋‹ค ์ •์ˆ˜ ํƒ€์ž…์˜ ์ŠคํŠธ๋ฆผ์ด ๋ฐฉ์ถœ๋จ
let disposable = Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
    .take(10) // parameter ๋งŒํผ์˜ ์ŠคํŠธ๋ฆผ ํ—ˆ์šฉ
    .subscribe(onNext: { value in
        print(value)
    }, onError: { error in
        print(error)
    }, onCompleted: {
        print("onCompleted")
    }, onDisposed: {
        print("onDisposed")
    })

// ์˜ˆ์™ธ ์ƒํ™ฉ ๋ฐœ์ƒ
// ์นด์šดํŒ…์ด ๋๋‚˜๊ธฐ ์ „์— ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ํ•ด์ œํ•ด ๋ฒ„๋ฆฐ๋‹ค๋ฉด?
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    disposable.dispose()
} // ๋ฐ˜ํ™˜๋œ disposable ๊ฐ์ฒด๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๊ฐ€ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ deinit ๋  ๋•Œ dispose ์‹คํ–‰

๋งŒ์•ฝ ๊ตฌ๋…๋ฐ›๋Š” Observable์ด ์—ฌ๋Ÿฌ๊ฐœ๋ผ๋ฉด?

→ DisposeBag์„ ์‚ฌ์šฉํ•˜์ž

extension Disposable {
    /// Adds `self` to `bag`
    ///
    /// - parameter bag: `DisposeBag` to add `self` to.
    public func disposed(by bag: DisposeBag) {
        bag.insert(self)
    }
}

ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋“ค์–ด์˜ค๋Š” DisposeBag ๊ฐ์ฒด์— ์ž์‹ ์„ insert ํ•œ๋‹ค.

๋ชจ๋“  disposable ๊ฐ์ฒด์— disposed๋ฅผ ํ•ด์ฃผ๋ฉด ํ•ด๋‹น ํŒŒ๋ผ๋ฏธํ„ฐ์ธ disposeBag์— ๋“ฑ๋ก๋˜๊ณ  disposeBag ๊ฐ์ฒด๊ฐ€ ํ•ด์ œ๋˜๋ฉด์„œ ๋“ฑ๋ก๋œ ๋ชจ๋“  disposable์ด ๋‹ค ๊ฐ™์ด dispose ๋˜์–ด๋ฒ„๋ฆฐ๋‹ค.

/// var๋กœ ์„ ์–ธํ•œ ์ด์œ : disposeBag์ด ํ•ด์ œ๋˜๋ฉด ๋ชจ๋“  disposable์ด dispose๋˜๋Š” ์›๋ฆฌ๋ฅผ ๊ฐœ๋ฐœ๋„์ค‘ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Œ
/// subscribe ์ค‘์ด๋˜ disposable์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ์‹ถ์œผ๋ฉด ์ƒˆ๋กœ์šด DisposeBag ๊ฐ์ฒด๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ๋!
var disposeBag = DisposeBag()

// interval: n์ดˆ๋งˆ๋‹ค ์ •์ˆ˜ ํƒ€์ž…์˜ ์ŠคํŠธ๋ฆผ์ด ๋ฐฉ์ถœ๋จ
Observable<Int>.interval(RxTimeInterval.seconds(1), scheduler: MainScheduler.instance)
    .take(10) // parameter ๋งŒํผ์˜ ์ŠคํŠธ๋ฆผ ํ—ˆ์šฉ
    .subscribe(onNext: { value in
        print(value)
    }, onError: { error in
        print(error)
    }, onCompleted: {
        print("onCompleted")
    }, onDisposed: {
        print("onDisposed")
    })
    .disposed(by: disposeBag) // **← ์—ฌ๊ธฐ ๋ถ€๋ถ„!**

 

RxSwift In 4Hours

๋™๊ธฐ/๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ

@IBAction func onLoadAsync(_ sender: Any) {
    // TODO: async
    DispatchQueue.global().async { [weak self] in
        guard let self = self else { return }
        let image = self.loadImage(from: self.IMAGE_URL)
        DispatchQueue.main.async {
            self.imageView.image = image
        }
    }
}

private func loadImage(from imageUrl: String) -> UIImage? {
    guard let url = URL(string: imageUrl) else { return nil }
    guard let data = try? Data(contentsOf: url) else { return nil }

    let image = UIImage(data: data)
    return image
}


just

Observable.just("Hello World")
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)

just์˜ ์ธ์ž๋กœ ๋„ฃ์–ด์ค€ "Hello World"๊ฐ€ str๋กœ ๋„˜์–ด๊ฐ„๋‹ค.

  • Observable.create()๋ฅผ ๋Œ€์‹  ํ•ด์ฃผ๋Š” ๋ฉ”์†Œ๋“œ
  • ๋ฐฐ์—ด์„ ๋„ฃ์œผ๋ฉด ๋ฐฐ์—ด์„ ์ฒ˜๋ฆฌํ•จ(์•„๋ฌด๊ฑฐ๋‚˜ ๋„ฃ์–ด๋„ ๋œ๋‹จ ์†Œ๋ฆฌ)


from

Observable.from(["RxSwift", "In", "4", "Hours"])
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)

from์— ๋ฐฐ์—ด์ด ๋“ค์–ด๊ฐ€๋ฉด ํ•˜๋‚˜์”ฉ ์‹คํ–‰ํ•œ๋‹ค.


map

Observable.just("Hello")
    .map { str in "\(str) RxSwift" }
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)

์‹คํ–‰์ˆœ์„œ

  1. just(Hello)
  2. map { str in "\(str) RxSwift" }
  3. onNext: { str in print(str) }
  4. ๊ฒฐ๊ณผ: Hello RxSwift
Observable.just(["Hello", "World"])
    .map { str in "\(str) RxSwift" }
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)

์‹คํ–‰์ˆœ์„œ

  1. just(["Hello", "World"])
  2. map { str in "\(str) RxSwift" }
  3. onNext: { str in print(str) }
  4. ๊ฒฐ๊ณผ: ["Hello", "World"] RxSwift
Observable.from(["Hello", "World"])
    .map { str in "\(str) RxSwift" }
    .subscribe(onNext: { str in
        print(str)
    })
    .disposed(by: disposeBag)

์‹คํ–‰์ˆœ์„œ

  1. just(["Hello", "World"]) → ์ฒซ๋ฒˆ์งธ ์›์†Œ
  2. map { str in "\(str) RxSwift" }
  3. onNext: { str in print(str) }
  4. ๊ฒฐ๊ณผ: Hello RxSwift
  5. just(["Hello", "World"]) → ๋‘๋ฒˆ์งธ ์›์†Œ
  6. map { str in "\(str) RxSwift" }
  7. onNext: { str in print(str) }
  8. ๊ฒฐ๊ณผ: World RxSwift


filter

Observable.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
    .filter { $0 % 2 == 0 }
    .subscribe(onNext: { n in
        print(n)
    })
    .disposed(by: disposeBag)

true์ผ ๋•Œ๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐ‘์œผ๋กœ ๋‚ด๋ ค๊ฐ


์ด๋ฏธ์ง€๋ฅผ ๋‹ค์šด๋ฐ›๋Š” ๊ณผ์ •

์œ„์—์„œ ๋ฐฐ์šด ๊ฑธ๋กœ ํ•ด์„ํ•ด๋ณด์ž.

Observable.just("800x600")
    .map { $0.replacingOccurrences(of: "x", with: "/") } // → "800/600"
    .map { "https://picsum.photos/\($0)/?random" } // → "https://picsum.photos/800/600/?random"
    .map { URL(string: $0) } // → URL ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
    .filter { $0 != nil } // → URL ๊ฐ์ฒด๊ฐ€ nil์ด๋ฉด ๋‚ด๋ ค๊ฐ€์ง€ ์•Š์Œ
    .map { $0! } // → ๋‚ด๋ ค์˜ค๋ฉด ๊ฐ•์ œ ์–ธ๋žฉํ•‘ (์œ„์—์„œ ์ฒดํฌํ–ˆ์œผ๋‹ˆ๊นŒ ๊ฐ•์ œ ์–ธ๋ž˜ํ•‘ํ•ด๋„ ๋จ)
    .map { try Data(contentsOf: $0) } // → URL์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์šด
    .map { UIImage(data: $0) } // → UIImage?
    .subscribe(onNext: { image in
        self.imageView.image = image
    })
    .disposed(by: disposeBag)


observeOn

Observable.just("800x600")
    .observeOn(ConcurrentDispatchQueueScheduler.init(qos: .default)) // ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ๋Œ๋ฆด ๋•Œ
    .map { $0.replacingOccurrences(of: "x", with: "/") } // → "800/600"
    .map { "https://picsum.photos/\($0)/?random" } // → "https://picsum.photos/800/600/?random"
    .map { URL(string: $0) } // → URL ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
    .filter { $0 != nil } // → URL ๊ฐ์ฒด๊ฐ€ nil์ด๋ฉด ๋‚ด๋ ค๊ฐ€์ง€ ์•Š์Œ
    .map { $0! } // → ๋‚ด๋ ค์˜ค๋ฉด ๊ฐ•์ œ ์–ธ๋žฉํ•‘ (์œ„์—์„œ ์ฒดํฌํ–ˆ์œผ๋‹ˆ๊นŒ ๊ฐ•์ œ ์–ธ๋ž˜ํ•‘ํ•ด๋„ ๋จ)
    .map { try Data(contentsOf: $0) } // → URL์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์šด
    .map { UIImage(data: $0) } // → UIImage?
    .observeOn(MainScheduler.instance) // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ๋Œ๋ฆฌ๋Š”๊ฑฐ
    .subscribe(onNext: { image in
        self.imageView.image = image
    })
    .disposed(by: disposeBag)

observeOn์„ ํ•ด์ค€ ๋‹ค์Œ์ค„ ๋ถ€ํ„ฐ ์˜ํ–ฅ์ด ๋ฏธ์นœ๋‹ค.


subscribeOn

subscribe๊ฐ€ ์‹คํ–‰๋˜๋Š” ์‹œ์ ๋ถ€ํ„ฐ ๋™์ž‘ํ•œ๋‹ค. ์•„๋ฌด ์œ„์น˜์—๋‚˜ ๋„ฃ์–ด๋„ ์ƒ๊ด€์—†์Œ!


side-effect

side-effect๋ฅผ ํ—ˆ์šฉํ•˜๋Š” ๊ฑด ๋‘ ๊ฐœ๊ฐ€ ์žˆ๋‹ค.

  • do
  • subscribe


์ฐธ๊ณ  ์ž๋ฃŒ

RxSwift + MVVM ์„ค๋ช…

[RxSwift] RxSwift ์‹œ์ž‘ํ•˜๊ธฐ

RxSwift ์•Œ์•„๋ณด๊ธฐ(ReactiveX ์— ๋Œ€ํ•ด์„œ) - 01 → ์‹œ๋ฆฌ์ฆˆ ์ฐธ๊ณ !

RxSwift 4์‹œ๊ฐ„์— ๋๋‚ด๊ธฐ (์ข…ํ•ฉํŽธ)

๋ฐ˜์‘ํ˜•