[RxSwift๐Ÿฆˆ] #7 Filtering Operators / Throttle & Debounce

๋˜์ƒยท2022๋…„ 1์›” 18์ผ
0

iOS

๋ชฉ๋ก ๋ณด๊ธฐ
6/42

raywenderlich RxSwift Ch5~6

1. Ignoring Operators

Swift ์˜ ๊ฒฝ์šฐ๋Š” ์ตœ์‹  ๋ฒ„์ ผ์—์„œ๋Š” ๋ฌธ๋ฒ•์— ๋งž๊ฒŒ, elementAt -> element(at: ), takeWhile -> take(while: ) ์ด๋Ÿฐ์‹์œผ๋กœ ๋ฐ”๋€Œ์–ด ์žˆ์—ˆ๋‹ค.

1. .ignoreElements()

  • .next ๋ฌด์‹œ
  • .completed, .error ๋Š” ํ—ˆ์šฉ

2. .element(at: index)

  • ํ•ด๋‹น index์— ์žˆ๋Š” ์š”์†Œ๋งŒ ๋ฐฉ์ถœ
  • index๋Š” 0๋ถ€ํ„ฐ ์‹œ์ž‘.

3. .filter( ((Any) -> Bool) )

  • Any ์—๋Š” ์•ž์˜ Observable ์—์„œ next ๋กœ ์˜ค๋Š” ๊ฐ’์ด ๋“ค์–ด๊ฐ„๋‹ค.
  • ์•ˆ์— ๋“ค์–ด๊ฐ€๋Š” ํ•จ์ˆ˜(ํด๋กœ์ €) ์˜ return ๊ฐ’์ด true ์ธ ๊ฒƒ๋งŒ ๋‚จ๋Š”๋‹ค.



2. Skipping Operators

1. .skip(count)

  • ์ฒ˜์Œ๋ถ€ํ„ฐ ๋›ฐ์–ด๋„˜์„ ๊ฐœ์ˆ˜ count ๋ฅผ ์ž…๋ ฅํ•ด์„œ ๋›ฐ์–ด๋„˜๋Š”๋‹ค.

2. .skip(while: ((Any) -> Bool) )

  • ์•ˆ์— ๋“ค์–ด๊ฐ€๋Š” ํ•จ์ˆ˜(ํด๋กœ์ €) ์˜ return ๊ฐ’์ด true ์ธ ๊ฒƒ์„ ๊ฑด๋„ˆ๋›ด๋‹ค.
  • ๊ทธ๋Ÿผ filter ๋ž‘ ๋ฐ˜๋Œ€์ด๊ธฐ๋งŒ ํ•˜๊ณ  ๋˜‘๊ฐ™์€๋ฐ ์™œ ์”€?
  • ์กฐ๊ฑด์— ๋งž์ง€ ์•Š๋Š” ํ•˜๋‚˜๋ฅผ ๋ณด๋‚ด๊ณ  ๋‚˜๋ฉด ๊ทธ ๋’ค์—๋Š” skip ํ•˜์ง€ ์•Š์Œ.
Observable.of(2, 2, 3, 4, 4)
    .skip(while: {
        // ์ด ๊ฒฝ์šฐ์—๋Š” ์กฐ๊ฑด์— ๋งž์ง€ ์•Š๋Š” 3๋ถ€ํ„ฐ ์ถœ๋ ฅ๋œ๋‹ค. 
        $0 % 2 == 0
    })
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

// 3 4 4

3. .skip(until: Observable)

  • ๋‹ค๋ฅธ observable ์ด .next ๋ฅผ emit ํ•˜๊ธฐ ์ „๊นŒ์ง€๋Š” ๋‹ค skip ๋จ.



3. Taking Operators

<-> Skipping

+) Observable ์—๋„ .enumerated() ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

1. take(count)

  • ์ฒ˜์Œ๋ถ€ํ„ฐ count ๋งŒํผ ๋ฐฉ์ถœ.

2. take(while: ((Any) -> Bool) )

  • ์ธ์ž๋กœ ๋ฐ›์€ ํ•จ์ˆ˜์˜ ๋ฐ˜ํ™˜๊ฐ’์ด true ์ธ ๊ฒƒ๋งŒ ๋ฐฉ์ถœ.
  • skipWhile ์ด skip ํ•˜๋‹ค๊ฐ€ ์กฐ๊ฑด์— ์•ˆ๋งž๋Š”๊ฒŒ ๋‚˜์™”์„ ๋•Œ๋ถ€ํ„ฐ ๋ฐฉ์ถœํ•˜๋˜ ๊ฒƒ๊ณผ ๋ฐ˜๋Œ€๋กœ
  • take ํ•˜๋‹ค๊ฐ€ ์กฐ๊ฑด์— ์•ˆ๋งž๋Š”๊ฒŒ ๋‚˜์™”์„ ๋•Œ๋ถ€ํ„ฐ ๋ฐฉ์ถœ ์•ˆํ•จ.

3. take(until: ((Any) -> Bool))

  • ๋‹ค๋ฅธ Observable ์ด .next ๋ฅผ emit ํ•˜๊ธฐ ์ „๊นŒ์ง€๋งŒ ๋ฐฉ์ถœ



4. Distinct Operators

์ค‘๋ณตํ•ด์„œ ์ด์–ด์ง€๋Š” ๊ฐ’์„ ์—†์• ์ค€๋‹ค.

1. .distinctUntilChanged()

  • ๋ฐ”๋กœ ์ด์–ด์ง€๋Š” ๊ฐ’์˜ ์ค‘๋ณต์„ ์—†์• ์ค€๋‹ค.
  • 1 1 2 2 1 ์„ 1 2 1 ๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค.

2. .distinctUntilChanged(_: ((Any) -> Void) )

  • distinctUntilChanged ์™€ ๊ธฐ๋ณธ ๋กœ์ง์€ ๊ฐ™๋‹ค ( ๋ฐ”๋กœ ์ด์–ด์ง€๋Š” ๊ฐ’์˜ ์ค‘๋ณต๋งŒ ์—†์• ์คŒ )
  • ํ•˜์ง€๋งŒ, custom ๊ฐ€๋Šฅ
distinctUntilChanged {
   $0.lastName == $1.lastName
}




5. Refactoring

#6์—์„œ ์ง„ํ–‰ํ–ˆ๋˜ ํ”„๋กœ์ ํŠธ๋ฅผ Filtering Object ๋ฅผ ์ด์šฉํ•ด์„œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ๋ฆฌํŒฉํ† ๋ง ํ•ด๋ณด์•˜๋‹ค. ์—ญ์‹œ ๋”ฐ๋ผ๊ฐ€๋ฉด์„œ ๊ธฐ์–ต์— ๋‚จ๋Š” ๊ฒƒ๋“ค ์œ„์ฃผ๋กœ ํฌ์ŠคํŒ…ํ•ด๋ณด๋ ค๊ณ  ํ•œ๋‹ค.


1. share()

Observable ์€ ๊ตฌ๋…๋  ๋•Œ๋งˆ๋‹ค create ๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๊ฐ™์€ Observable ์„ ๊ตฌ๋…ํ•ด๋„ ๋‹ค๋ฅธ ๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋‹ค. ๊ฒŒ๋‹ค๊ฐ€ ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ๊ฑด๋ฐ Observable ์„ ๊ตฌ๋…์ž ๋ณ„๋กœ ๋‹ค๋ฅด๊ฒŒ ์œ ์ง€ํ•˜๋Š” ๊ฒƒ์€ ๋น„ํšจ์œจ์ ์ด๋‹ค.

๊ทธ๋ž˜์„œ ํ•˜๋‚˜์˜ ๊ตฌ๋…์„ ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•ด์„œ share() ๋ฅผ ์ด์šฉํ•œ๋‹ค.

let newImage = photosViewController.selectedPhotos.share()
// newImage ๋ฅผ ๊ตฌ๋…ํ•˜์—ฌ ๊ฐ๊ฐ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

  • create : share() ๋ฅผ ์ด์šฉํ•˜๋ฉด ๊ตฌ๋…์ž ์ˆ˜๊ฐ€ 1์ด ๋  ๋•Œ ๊ตฌ๋…์„ ์ƒ์„ฑํ•˜๊ณ , ์ด๋ฏธ ์ƒ์„ฑ๋œ ๊ตฌ๋…์„ ๋‹ค๋ฅธ ๊ตฌ๋…์ž์™€ ๊ณต์œ ํ•œ๋‹ค.
  • dispose : ๊ณต์œ ๋œ sequence ์— ๋Œ€ํ•œ ๋ชจ๋“  ๊ตฌ๋…์ด dispose ๋˜๋ฉด ๊ณต์œ ๋œ sequence ๋„ dispose ํ•œ๋‹ค.
  • method
    • share() : ๊ตฌ๋…์ด ์˜ํ–ฅ์„ ๋ฐ›๊ธฐ ์ „๊นŒ์ง€๋Š” ์–ด๋–ค ๊ฐ’๋„ ๋ฐฉ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค.
    • share(replay: , scope: ) ๋Š” ๋งˆ์ง€๋ง‰ ๋ช‡ ๊ฐœ์˜ ๋ฐฉ์ถœ๊ฐ’์— ๋Œ€ํ•œ ๋ฒ„ํผ๋ฅผ ์ œ๊ณตํ•œ๋‹ค. (์ƒˆ๋กœ์šด ๊ตฌ๋…์ž์—๊ฒŒ ๋งˆ์ง€๋ง‰ ๋ช‡ ๊ฐœ๋ฅผ ๋ฐ”์ถœํ•œ๋‹ค.)
    • sharing ์˜ ๊ฒฝ์šฐ completed ๋˜์ง€ ์•Š๋Š” Observable ์— ์‚ฌ์šฉํ•œ๋‹ค.

2. caching

Observable ์€ ํ˜„์žฌ ๊ฐ’์ด๋‚˜ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ํŠน์ • ์š”์†Œ๋ฅผ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ฐ ์š”์†Œ ์ž์ฒด๊ฐ€ ์Šค์Šค๋กœ๋ฅผ ์ถ”์ ํ•˜๊ฒŒ ํ•ด์•ผํ•œ๋‹ค.

// imageCache ๋ฐฐ์—ด๋กœ, ์ด๋ฏธ ์žˆ๋Š” ์ด๋ฏธ์ง€์ธ์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์ณค๋‹ค.
.filter { [weak self] newImage in
  // ๋ณดํ†ต assetId ๊ฐ™์€ ๊ฒƒ์„ ์ด์šฉํ•˜์ง€๋งŒ, ์˜ˆ์ œ๋ผ์„œ ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋น„๊ต.
  let len = newImage.pngData()?.count ?? 0
  guard self?.imageCache.contains(len) == false else { return false }
  self?.imageCache.append(len)
  return true
}

3. ์ฝ”๋“œ ๋ฆฌํŒฉํ† ๋ง์—์„œ ์ค‘์š”ํ•œ ๊ฒƒ.

10์ค„์งœ๋ฆฌ ์ฝ”๋“œ๋ฅผ 3์ค„๋กœ ์ค„์ด๋Š” ๊ฒƒ๋งŒ์ด ์ข‹์€ ๋ฆฌํŒฉํ† ๋ง ๋ฐฉ์‹์ด ์•„๋‹ˆ๋‹ค!! ์˜ˆ์ œ์—์„œ๋Š” PHPhotoLibrary ์— ์‚ฌ์ง„ ์ ‘๊ทผ ๊ถŒํ•œ ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ์“ธ ๋•Œ, ์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์งฐ๋‹ค.

// ์Šน์ธ์ด ๋˜์–ด ์žˆ์œผ๋ฉด true 
// ์•ˆ๋˜์–ด ์žˆ์œผ๋ฉด false ๋ณด๋‚ด๊ณ  ๋‹ค์‹œ ๊ถŒํ•œ ์š”์ฒญํ•ด์„œ ๊ฒฐ๊ณผ๊ฐ’
extension PHPhotoLibrary {
  static var authorized: Observable<Bool> {   
    return Observable.create() { observer in  
    
      if authorizationStatus() == .authorized {
        observer.onNext(true)
        observer.onCompleted()
        
      } else {
        observer.onNext(false)
        requestAuthorization { newStatus in
          observer.onNext(newStatus == .authorized)
          observer.onCompleted()
        }
      }
      return Disposables.create()
    }
  }
}
  1. ๊ผญ ์“ธ ํ•„์š”๊ฐ€ ์—†์–ด๋„ ์ฝ”๋“œ๋ฅผ ํ•œ๋‘์ค„ ์ถ”๊ฐ€ํ•ด์„œ ์˜๋ฏธ๊ฐ€ ๋ช…ํ™•ํ•ด์ง€๊ณ  ์ฝ๊ธฐ ํŽธํ•ด์ง„๋‹ค๋ฉด ๊ทธ๋ ‡๊ฒŒ ํ•˜์ž!
// ์–ด์ฐจํ”ผ ์•ฑ์„ ๋งจ ์ฒ˜์Œ ์ผœ๊ณ  ์ฒซ๋ฒˆ์งธ ๊ฐ’์€ false ์ž„.
// ๊ถŒํ•œ ์Šน์ธ์„ ํ•ด์ค˜์„œ ๊ฐ’์ด true ์ผ ๋•Œ
let authorized = PHPhotoLibrary.authorized.share()
authorized
     .skipWhile { $0 == false }
     .take(1) // ์“ธ ํ•„์š”๊ฐ€ ์—†์ง€๋งŒ ์˜๋ฏธ๊ฐ€ ๋ช…ํ™•ํ•ด์ง„๋‹ค.
     .subscribe(onNext: { [weak self] _ in
         self?.photos = PhotosViewController.loadPhotos()
         DispatchQueue.main.async {
             self?.collectionView?.reloadData()
         }
     })
     .disposed(by: bag)

filter{$0 == false} == filter{!$0} == filter{!}
๋ฌผ๋ก  ์ค„์—ฌ์„œ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์€ ์ค„์—ฌ ์จ๋„ ๊ดœ์ฐฎ์Œ.

  1. Observable ์—์„œ ๋„˜์–ด์˜ค๋Š” ๋ฐ์ดํ„ฐ์˜ sequence ๊ฐ€ ์ ˆ๋Œ€๋ถˆ๋ณ€ ํ•  ๋กœ์ง์ด ์•„๋‹ˆ๋ผ๋ฉด ๋กœ์ง์„ ์ƒ๋žตํ•˜์ง€ ๋ง์ž.
// ๊ถŒํ•œ ์Šน์ธ์„ ๊ฑฐ์ ˆํ•ด์„œ ๊ฐ’์ด false ์ผ ๋•Œ
// ์–ด์ฐจํ”ผ ์•ฑ ์ฒ˜์Œ ์ผ  false, ๊ถŒํ•œ ์Šน์ธ ๊ฑฐ์ ˆ false ๋กœ 2๊ฐœ์˜ ๊ฐ’์ด ์˜ค์ง€๋งŒ skip, takeLast๋ฅผ ๋‘˜ ๋‹ค ํ•œ ๊ฒƒ ์ฒ˜๋Ÿผ ๋กœ์ง์„ ์ƒ๋žตํ•˜์ง€ ๋ง์ž.
// 
_ = authorized
      .skip(1)
      .takeLast(1)
      .filter { $0 == false }
      .subscribe(onNext: { [weak self] _ in
        guard let errorMessage = self?.errorMessage else { return }
        DispatchQueue.main.async(execute: errorMessage)
      })

3. throttle

๋„ˆ๋ฌด ๋งŽ์€ ์‚ฌ์šฉ์ž ์ž…๋ ฅ๊ฐ’์„ ๊ฑธ๋Ÿฌ์ฃผ๋Š” ์ฝ”๋“œ๋กœ
์—ฌ๋Ÿฌ๋ฒˆ ๋“ค์–ด์˜ค๋Š” ๋™์ผ ์ด๋ฒคํŠธ๋ฅผ ํ•œ๋ฒˆ๋งŒ ์ „๋‹ฌํ•œ๋‹ค.

// 0.5 ์ดˆ๊ฐ„ ๋“ค์–ด์˜ค๋Š” ์ž…๋ ฅ์„ ๋ชจ์•„์„œ ๋ฐ˜์‘ํ•œ๋‹ค.
// ์ฆ‰๊ฐ์ ์œผ๋กœ ๋ฐ˜์‘ํ•˜์ง€ ์•Š๊ฒŒ!
.throttle(0.5, scheduler: MainScheduler.instance)

์šฐ๋ฆฌ๊ฐ€ ๋งŒ๋“  ์˜ˆ์ œ์—์„œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„์„ ์„ ํƒํ•  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์ฝœ๋ผ์ฃผ๋ฅผ ๋งŒ๋“œ๋Š”๊ฒŒ ์ž์› ๋‚ญ๋น„์ด๋ฏ€๋กœ, 0.5์ดˆ ๋‚ด์— ์„ ํƒ๋˜๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ ์‚ฌ์ง„์— ๋Œ€ํ•ด์„œ๋Š” ์ผ์ผ์ด ์ฝœ๋ผ์ฃผ๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๊ฒŒ throttle ์„ ๊ฑธ์—ˆ๋‹ค.


์ด ์™ธ์—๋„ textField ์ž…๋ ฅํ•  ๋•Œ๋‚˜
๋ชจ๋‹ฌ์ด ๋œจ๊ธฐ ์ „์— ๋ฒ„ํŠผ์„ ์—ฐํƒ€ํ–ˆ์„ ๋•Œ ๋ชจ๋‹ฌ์ด ์—ฌ๋Ÿฌ๊ฐœ ๋œจ์ง€ ์•Š๋„๋ก ๋ฐฉ์ง€ ํ•œ๋‹ค๋˜๊ฐ€
๋“œ๋ž˜๊ทธ ํ•  ๋•Œ ๋“œ๋ž˜๊ทธ ๋๋‚˜๋Š” ์œ„์น˜๋งŒ ์•Œ๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.


4. Debounce

debounce ๋Š” ์˜ˆ์ œ์—๋Š” ์—†์—ˆ์ง€๋งŒ throttle ๊ณผ ํ•ญ์ƒ ๋ฌถ์—ฌ์„œ ๋‹ค๋‹ˆ๋Š” ๊ฐœ๋…์ธ ๊ฒƒ ๊ฐ™์•„์„œ ํ•จ๊ป˜ ์ฐพ์•„๋ดค๋‹ค.

debounce ๋Š” ์ผ์ • ์‹œ๊ฐ„๋™์•ˆ ๋ฐœ์ƒํ•˜๋Š” ์ด๋ฒคํŠธ๋ฅผ ๋ชจ์•„์„œ!! ํ•œ๋ฒˆ์— ์ „๋‹ฌํ•œ๋‹ค.

debounce(for: .seconds(2), lastest: true, scheduler: MainScheduler.instance)

์ง€์ •๋œ ์‹œ๊ฐ„ 2์ดˆ ์•ˆ์—์„œ ๋งˆ์ง€๋ง‰์— ๋“ค์–ด์˜จ ๊ฒƒ์„ emitํ•˜๋Š” ์˜ต์…˜์ธ lastest ๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์ด true! (throttle ๋„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋‹ค.)

latest: false ์ด๋ฉด ์ฒซ๋ฒˆ์งธ ๊ฐ’๋งŒ์„ emit ํ•˜๊ณ  2์ดˆ ๋™์•ˆ ๋“ค์–ด์˜ค๋Š” ๊ฐ’์€ ๋‹ค ๋ฌด์‹œํ•œ ๋’ค, 2์ดˆ๊ฐ€ ์ง€๋‚˜๊ณ  ๋‚˜์„œ ์–ธ์  ๊ฐ€ ๋˜ ๋“ค์–ด์˜ค๋Š” ์ฒซ๋ฒˆ์งธ ๊ฐ’๋งŒ emit ํ•˜๊ฒŒ ๋œ๋‹ค.



๋Š๋‚€์ 

filtering operator ๊ฐ€ ๊ทธ๋ ‡๊ฒŒ ์–ด๋ ต์ง„ ์•Š๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋Š”๋ฐ, takeWhile, skipWhile ์ด ์ƒ๊ฐ๋ณด๋‹ค ํ—ท๊ฐˆ๋ ธ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์—ฌ๊ธฐ ์žˆ๋Š”๊ฒŒ ๋‹ค๊ฐ€ ์•„๋‹ˆ๋ผ ๋˜ ์•„์ฃผ ๋งŽ์€.. operator ๋“ค์ด ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋‹ˆ๊นŒ ์ ์‹œ์ ๊ธฐ์— ๋งž๋Š” ๊ฒƒ์„ ์ฐพ์•„ ์“ฐ๋Š” ๊ฒƒ๋„ ์–ด๋ ค์šธ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

PHPhotoLibrary.autorized ๋ฅผ Observable ์„ ์ด์šฉํ•ด์„œ ๋‹ค๋ฃจ๋Š”๊ฒŒ ์‹ ๊ธฐํ–ˆ๋‹ค. ์—ฌํƒœ ํ•จ์ˆ˜ return ๊ฐ’์œผ๋กœ๋งŒ ์คฌ์—ˆ๋Š”๋ฐ, computed property ๋ฅผ ์‚ฌ์šฉํ•ด์„œ๋„ Observable ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š”๊ฒŒ.. ๋ฐฐ์šธ ๋ถ€๋ถ„์ด ์—„์ฒญ ๋งŽ์€ ๊ฒƒ ๊ฐ™๋‹ค.



์ถœ์ฒ˜ - https://github.com/fimuxd/RxSwift/tree/master/Lectures
์ด๋ฏธ์ง€ ์ถœ์ฒ˜ - https://reactivex.io/

profile
0๋…„์ฐจ iOS ๊ฐœ๋ฐœ์ž์ž…๋‹ˆ๋‹ค.

0๊ฐœ์˜ ๋Œ“๊ธ€