๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

๐ŸŽ iOS/RxSwift

[RxSwift] Traits - Driver, Signal, Control Property/ControlEvent

์•ˆ๋…•ํ•˜์„ธ์š” ์ œ์ธ์ž…๋‹ˆ๋‹ค :) 

์ง€๋‚œ ๊ฒŒ์‹œ๊ธ€์— ์ด์–ด ์ด๋ฒˆ ๊ฒŒ์‹œ๊ธ€์—์„œ๋Š” RxCocoa์˜ Traits์— ๋Œ€ํ•ด ์ •๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์ด๋ฒˆ ๊ฒŒ์‹œ๊ธ€๋„ ReactiveX/RxSwift์˜ Traits ๋ฌธ์„œ ๋ฒˆ์—ญ์„ ํ†ตํ•ด ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!

RxCocoa Traits์˜ ์ข…๋ฅ˜

RxCocoa์˜ Traits๋กœ๋Š” Driver, Signal, Control Property/ControlEvent๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!

Driver

Driver๋Š” ๊ฐ€์žฅ ์ •๊ตํ•œ trait์ž…๋‹ˆ๋‹ค. UI ๊ณ„์ธต์—์„œ reactive ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์ง๊ด€์ ์ธ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•˜๊ฑฐ๋‚˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ๋™(driving)ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์„ ๋ชจ๋ธ๋งํ•˜๋ ค๋Š” ๋ชจ๋“  ๊ฒฝ์šฐ์— ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

+ Driver๋ผ๋Š” ์ด๋ฆ„์ด ๋ถ™์€ ์ด์œ ? Driver์˜ ๋ชฉ์  ์ž์ฒด๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ๋™(drive)ํ•˜๋Š” ์‹œํ€€์Šค๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ

Driver์˜ ํŠน์ง•

1. ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. 

์‹œํ€€์Šค ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์‚ฌ์šฉ์ž ์ž…๋ ฅ์— ๋Œ€ํ•œ ์‘๋‹ต์„ ๋ฉˆ์ถ”๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

2. observe๋Š” Main Scheduler์—์„œ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

UI ์š”์†Œ๋“ค๊ณผ ๋กœ์ง์€ ์ผ๋ฐ˜์ ์œผ๋กœ thread safe ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ด๋Ÿฌํ•œ ์š”์†Œ๋“ค์ด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ๊ด€์ฐฐ๋˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

 

3. ๋ถ€์ˆ˜์ž‘์šฉ์„ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.(share(replay: 1, scope: .whileConnected))

Driver๋Š” ๋ถ€์ˆ˜์ž‘์šฉ์„ ๊ณต์œ ํ•˜๋Š” observable ์‹œํ€€์Šค๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

share(replay: , scope: )
ํ•œ๋ฒˆ ์ƒ์„ฑํ•œ ์‹œํ€€์Šค๋ฅผ ๊ณต์œ ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜๋Š” operator์ž…๋‹ˆ๋‹ค. share๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐœ์ƒํ•œ ์ด๋ฒคํŠธ๊ฐ€ ๋ฒ„ํผ์— ์ €์žฅ๋˜๊ณ , ์ƒˆ๋กœ์šด subscription์€ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ ์‹œํ€€์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ๋ฒ„ํผ์— ์ €์žฅ๋œ ์ด๋ฒคํŠธ๋ฅผ ์ „๋‹ฌ๋ฐ›๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. 

- replay: ๋ฒ„ํผ ์‚ฌ์ด์ฆˆ(์‹ ๊ทœ subscriber์—๊ฒŒ ์ด์ „ ๋ฐฉ์ถœํ–ˆ๋˜ ์š”์†Œ๋ฅผ ๋ช‡ ๊ฐœ๋ฅผ ๊ธฐ๋กํ–ˆ๋‹ค๊ฐ€ ๋ฐฉ์ถœํ• (replay) ๊ฒƒ์ธ๊ฐ€)
- scope
    1) .whileConnected: subscriber๊ฐ€ ํ•œ ๊ฐœ ์ด์ƒ ์žˆ์œผ๋ฉด replay๊ฐ€ ์œ ์ง€๋˜์ง€๋งŒ, dispose๋˜์–ด 0๊ฐœ๊ฐ€ ๋˜๋ฉด replay ๋ฒ„ํผ ์ดˆ๊ธฐํ™”
    2) .forever: subscriber๊ฐ€ ์—†์–ด๋„ ๋ฒ„ํผ ์œ ์ง€

์˜ˆ์ œ ์ฝ”๋“œ

let results = query.rx.text
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
    }

results
    .map { "\($0.count)" }
    .bind(to: resultCount.rx.text)
    .disposed(by: disposeBag)

results
    .bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .disposed(by: disposeBag)

์œ„์˜ ์ฝ”๋“œ์˜ ์˜๋„๋œ ๋™์ž‘์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

 

1. ์‚ฌ์šฉ์ž input ์กฐ์ ˆ(throttle)

throttle operator
throttle์€ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์ตœ์†Œ ๊ฐ„๊ฒฉ์„ ์ œํ•œํ•  ์ˆ˜ ์žˆ๋Š” operator์ž…๋‹ˆ๋‹ค. 
์ง€์ •๋œ ๊ธฐ๊ฐ„(dueTime ์„ค์ •) ์•ˆ์— ์ˆ˜๋งŽ์€ ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•ด๋„, 2๊ฐœ ์ด์ƒ ์š”์†Œ๊ฐ€ ๋ฐฉ์ถœ๋˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

2. ์„œ๋ฒ„ ์š”์ฒญ์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž ๊ฒฐ๊ณผ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ค๊ธฐ (์ฟผ๋ฆฌ๋‹น ํ•œ ๋ฒˆ)

3. ๊ฒฐ๊ณผ๋ฅผ ๋‘ UI ์š”์†Œ์— ๋ฐ”์ธ๋”ฉ (reulstCount, resultsTableView)

 

์ด ์ฝ”๋“œ์˜ ๋ฌธ์ œ์ ์€ ๋ฌด์—‡์ผ๊นŒ์š”?

 

  • ๋งŒ์•ฝ fetchAutoCompleteItems ์‹œํ€€์Šค์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ์˜ค๋ฅ˜๋Š” ๋ชจ๋“  ๋ฐ”์ธ๋”ฉ์„ ํ•ด์ œํ•˜๊ณ  UI๊ฐ€ ๋” ์ด์ƒ ์ƒˆ ์ฟผ๋ฆฌ์— ์‘๋‹ตํ•˜์ง€ ์•Š๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • fetchAutoCompleteItems๊ฐ€ ์ผ๋ถ€ ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉด ๊ฒฐ๊ณผ๊ฐ€ UI ์š”์†Œ์— ๋ฐ”์ธ๋”ฉ๋˜๋Š” ๊ณผ์ •์—์„œ ์ถฉ๋Œ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ๋ฅผ ๋‘ ๊ฐœ์˜ UI ์š”์†Œ์— ๋ฐ”์ธ๋”ฉํ•˜๋ฉด์„œ, ๊ฐ™์€ ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ๋‘ ๊ฐœ์˜ HTTP ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. (์˜๋„๋œ ๋™์ž‘์ด ์•„๋‹˜)

๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ฐœ์„ ํ•˜์—ฌ ์œ„์˜ ๋ฌธ์ œ์ ๋“ค์„ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

let results = query.rx.text
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .observeOn(MainScheduler.instance)  // results are returned on MainScheduler
            .catchErrorJustReturn([])           // in the worst case, errors are handled
    }
    .share(replay: 1)                           // HTTP requests are shared and results replayed
                                                // to all UI elements

results
    .map { "\($0.count)" }
    .bind(to: resultCount.rx.text)
    .disposed(by: disposeBag)

results
    .bind(to: resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .disposed(by: disposeBag)

์ฒซ๋ฒˆ์งธ ์ฝ”๋“œ์™€์˜ ์ฐจ์ด์ ์„ ๋ณด๋ฉด,

.observeOn(MainScheduler.instance) ๋ฅผ ํ†ตํ•ด ๋ฉ”์ธ ์Šค์ผ€์ค„๋Ÿฌ์—์„œ ์ž‘์—…์ด ์ˆ˜ํ–‰๋˜๋„๋ก ํ•˜๊ณ , .catchErrorJustReturn()์„ ํ†ตํ•ด ์—๋Ÿฌ ์ƒํ™ฉ์— ๋Œ€๋น„(error ๋ฐœ์ƒ ์‹œ [] ์ „๋‹ฌ)ํ•ฉ๋‹ˆ๋‹ค. 

๋˜ํ•œ, .share(replay: 1) ์„ ํ†ตํ•ด ์ƒ์„ฑ๋œ ์‹œํ€€์Šค๋ฅผ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

 

์œ„์™€ ๊ฐ™์€ ๋ฐฉ๋ฒ•์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๋Œ€๊ทœ๋ชจ ์‹œ์Šคํ…œ์—์„œ ์œ„์™€ ๊ฐ™์€ ์‚ฌํ•ญ๋“ค์ด ์ ์ ˆํ•˜๊ฒŒ ์ฒ˜๋ฆฌ๋˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๊ฒƒ์€ ์–ด๋ ค์šธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. -> Driver๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋” ๊ฐ„๋‹จํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

let results = query.rx.text.asDriver()        // This converts a normal sequence into a `Driver` sequence.
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .asDriver(onErrorJustReturn: [])  // Builder just needs info about what to return in case of error.
    }

results
    .map { "\($0.count)" }
    .drive(resultCount.rx.text)               // If there is a `drive` method available instead of `bind(to:)`,
    .disposed(by: disposeBag)              // that means that the compiler has proven that all properties
                                              // are satisfied.
results
    .drive(resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .disposed(by: disposeBag)

์œ„์˜ ์ฝ”๋“œ์—์„œ๋Š”,

.asDriver()๋ฅผ ํ†ตํ•ด ControlProperty trait๋ฅผ Driver trait๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. (๋ชจ๋“  observable sequence๋Š” Driver๋กœ ๋ณ€๊ฒฝ๊ฐ€๋Šฅ)

.asDriver(onErrorJustReturn: []) ๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

.bind(to:) ๋Œ€์‹  drive()๋ฅผ ์‚ฌ์šฉํ•ด UI์š”์†Œ์— ์ ์šฉํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

drive๋Š” Driver์— ์ •์˜๋œ ๋ฉ”์„œ๋“œ๋กœ, drive๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํ•ด๋‹น observable ์‹œํ€€์Šค๋Š” ์ ˆ๋Œ€ ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ถœํ•˜์ง€ ์•Š์œผ๋ฉฐ UI ์š”์†Œ์— ๋ฐ”์ธ๋”ฉํ•˜๊ธฐ์— ์•ˆ์ „ํ•œ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ๊ด€์ฐฐ๋ฉ๋‹ˆ๋‹ค. 

Signal

Signal์€ Driver์™€ ์œ ์‚ฌํ•˜์ง€๋งŒ ๊ตฌ๋… ์‹œ ์ตœ์‹ ์˜ ์ด๋ฒคํŠธ๋ฅผ replay ํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ฐจ์ด์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.

(Driver๋Š” ๊ตฌ๋…ํ•˜๋Š” ์ˆœ๊ฐ„ ์ตœ์‹ ์˜ ์ด๋ฒคํŠธ๋ฅผ replay, Signal์€ ๊ตฌ๋…ํ•œ ์ดํ›„์˜ ๊ฐ’๋งŒ์„ ๋ฐฉ์ถœ)

Signal์˜ ํŠน์ง•

  • ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ถœํ•˜์ง€ ์•Š์Œ
  • Main Scheduler์—์„œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐฉ์ถœ
  • ๋ถ€์ˆ˜์ž‘์šฉ์„ ๊ณต์œ  (share(scope: .whileConnected) but Driver์™€ ๋‹ค๋ฅด๊ฒŒ ๋ฒ„ํผ๋ฅผ ๊ฐ€์ง€์ง€ ์•Š์Œ) 
  • ๊ตฌ๋… ์ด์ „์˜ event๋ฅผ replay ํ•˜์ง€ ์•Š์Œ

ControlProperty / ControlEvent

ControlProperty

UI ์š”์†Œ์˜ property๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” Observable/ObservableType ์„ ์œ„ํ•œ Trait์ž…๋‹ˆ๋‹ค.

ControlProperty ์‹œํ€€์Šค๋Š” ์˜ค์ง ์ตœ์ดˆ์˜ control value์™€ ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์ž‘ํ•œ ๊ฐ’ ๋ณ€๊ฒฝ๋งŒ์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. (์ฝ”๋“œ๋กœ ๋ณ€๊ฒฝ๋œ value change๋Š” ๊ฐ์ง€ํ•˜์ง€ ๋ชปํ•จ)

ControlProperty์˜ ํŠน์ง•

  • ์ ˆ๋Œ€ ์‹คํŒจํ•˜์ง€ ์•Š์Œ
  • share(replay: 1) ์ฒ˜๋Ÿผ ๋™์ž‘ - subscribe ํ˜ธ์ถœ ์‹œ ๋งˆ์ง€๋ง‰ element๊ฐ€ ์ƒ์„ฑ๋˜๋Š” ์ฆ‰์‹œ replay 
  • deallocated๋  ๋•Œ complete๋จ (๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ ์‹œ complete ์ด๋ฒคํŠธ ๋ฐฉ์ถœ)
  • ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ถœํ•˜์ง€ ์•Š์Œ
  • Main Thread์—์„œ ์‹คํ–‰๋จ

ControlProperty์˜ ์‹คํ–‰์€ ์ด๋ฒคํŠธ ์‹œํ€€์Šค๊ฐ€ main scheduler์—์„œ subscribe๋˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. (subscribeOn(ConcurrentMainScheduler.instance))

ControlEvent

UI ์š”์†Œ์˜ event๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” Observable/ObservableType ์„ ์œ„ํ•œ Trait์ž…๋‹ˆ๋‹ค.

ControlEvent์˜ ํŠน์ง•

  • ์ ˆ๋Œ€ ์‹คํŒจํ•˜์ง€ ์•Š์Œ
  • ๊ตฌ๋… ์‹œ ์ดˆ๊ธฐ ๊ฐ’์„ ๋‚ด๋ณด๋‚ด์ง€ ์•Š์Œ
  • deallocated๋  ๋•Œ complete๋จ (๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ ์‹œ complete ์ด๋ฒคํŠธ ๋ฐฉ์ถœ)
  • ์—๋Ÿฌ๋ฅผ ๋ฐฉ์ถœํ•˜์ง€ ์•Š์Œ
  • Main Thread์—์„œ ์‹คํ–‰๋จ

ControlEvent์˜ ์‹คํ–‰ ๋˜ํ•œ ์ด๋ฒคํŠธ ์‹œํ€€์Šค๊ฐ€ main scheduler์—์„œ subscribe๋˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค. (subscribeOn(ConcurrentMainScheduler.instance))


[์ฐธ๊ณ  ์ž๋ฃŒ]

https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Traits.md

https://inuplace.tistory.com/1102

https://ios-development.tistory.com/872

'๐ŸŽ iOS > RxSwift' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[RxSwift] Traits - Single, Completable, Maybe  (0) 2023.04.17
[RxSwift] Relay๋ž€?  (0) 2023.02.17
[RxSwift] Subject๋ž€?  (0) 2023.02.03
[RxSwift] Observable  (0) 2022.05.02
[RxSwift] RxSwift์™€ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ  (7) 2022.04.07