๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
IOS๐ŸŽ/RxSwift

[RxSwift] Driver๋ž€ ? (feat. bind)

by Jouureee 2022. 3. 21.

 

observable์„ ์ƒ์„ฑํ•˜๊ณ  ๊ตฌ๋… ํ•  ๋•Œ ๊ฐ€๋” subscribe ๋Œ€์‹  asDriver(), drive()๋กœ ๋ฐ›๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค .. ๋ญ์ง€ ?? ํ•˜๋ฉด์„œ ์žŠ๊ณ  ์‚ด๋˜ ์ค‘ traits ๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ์ฝ๊ณ  

driver๊ฐ€ ์žˆ์–ด ์ •๋ฆฌ ํ•ด๋ณด๋ ค๊ตฌ ํ•ฉ๋‹ˆ๋‹ค !

์ฐธ๊ณ  ๋ฌธ์„œ : https://github.com/ReactiveX/RxSwift/blob/main/Documentation/Traits.md#driver

 

GitHub - ReactiveX/RxSwift: Reactive Programming in Swift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com

 


 

Driver

 UI layer์—์„œ ์ข€ ๋” ์ง๊ด€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋„๋ก ์ œ๊ณตํ•˜๋Š” trait

 

trait์ด๋ž€ ?

"๋ชจ๋“  ๊ฒฝ๊ณ„์—์„œ ์‚ฌ์šฉํ• ์ˆ˜ ์žˆ๋Š” ์›์‹œ Observable๊ณผ ๋น„๊ตํ• ๋•Œ ์ธํ„ฐํŽ˜์ด์Šค ๊ฒฝ๊ณ„์—์„œ observable ํ”„๋กœํผํ‹ฐ๋ฅผ ์ „๋‹ฌํ•˜๊ณ  ๋ณด์žฅํ•˜๋ฉฐ, ๋ฌธ๋ฒ•์ ์œผ๋กœ๋„ ๋” ์‰ฝ๊ณ  ๊ตฌ์ฒด์ ์ธ ์‚ฌ์šฉ ์‚ฌ๋ก€๋ฅผ ํƒ€์ผ“ํŒ…ํ•˜๋Š”๋ฐ ๋„์›€์ด๋ฉ๋‹ˆ๋‹ค" 

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

 

๋‹ค์‹œ Driver๋กœ ๋Œ์•„์™€์„œ ..

Driver์˜ ํŠน์ง•

  • ์˜ค๋ฅ˜๋ฅผ ๋ฐฉ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค
  • observe๋Š” Main scheulder์—์„œ ๋ฐœ์ƒํ•œ๋‹ค.
  • ๋ถ€์ž‘์šฉ์„ ๊ณต์œ ํ•œ๋‹ค (?) ex> share(replay: 1, scope: .whileConnected)

๋ถ€์ž‘์šฉ์„ ๊ณต์œ ํ•œ๋‹ค๋Š” ๋ง์ด ๋ฌด์Šจ ๋ง์ธ๊ฐ€ ํ•ด์„œ ๋ณด๋‹ˆ ์•„๋ž˜ ์˜ˆ์‹œ๊ฐ€ ๋‚˜์™€ ์žˆ๋„ค์š”

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. query์— ๋Œ€ํ•ด fetchAutoCompleteItems (์„œ๋ฒ„์—์„œ query)๊ฒฐ๊ณผ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ ์ˆ˜ํ–‰

2. ๊ฐ€์ ธ์˜จ resultfmf resultCount, tableview UI์— ๋ฐ”์ธ๋”ฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ ์œ„์—์„œ ๋งŒ์•ฝ fetchAutoCompleteItems ์ˆ˜ํ–‰์‹œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์–ด๋–กํ• ๊นŒ์š” ? ๋ฐ˜ํ™˜๊ฐ’์ด ์—†์„์ˆ˜๋„ ์žˆ๊ณ  ํŒŒ์‹ฑ์ด ์ž˜๋ชป๋  ์ˆ˜๋„ ์žˆ์„๊ฑฐ ๊ฐ™๋„ค์š”. ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ๋ฐ”์ธ๋”ฉ์ด ํ•ด์ œ๋˜๊ณ  UI๊ฐ€ ๋”์ด์ƒ ์ƒˆ๋กœ์šด ์ฟผ๋ฆฌ์— ๋Œ€ํ•ด ์—…๋ฐ์ดํŠธ ๋˜์ง€ ์•Š์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๋˜ํ•œ fetchAutoCompleteItems ์ž‘์—… ์ค‘ ์ผ๋ถ€๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰ ๋  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  UI๋Š” ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰๋˜์–ด์•ผ ํ•˜๋Š”๋ฐ ๋ง์ด์ฃ .

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

 

๋”ฐ๋ผ์„œ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •๋˜์–ด์•ผ ํ•  ๊ฒƒ์ž…๋‹ˆ๋‹ค.

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)

 

๋˜๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ์ž‘์„ฑ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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)

๋ฐ”๋€ ์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด 

let results = query.rx.text.asDriver()

asDriver : ControlProperty trait์„ Driver trait์œผ๋กœ ๋ฐ”๊พธ์—ˆ์Šต๋‹ˆ๋‹ค.

 

๋‘๋ฒˆ์งธ ๋ณ€ํ™”๋Š”

 

.asDriver(onErrorJustReturn: [])โ€‹

observable ์‹œํ€€์Šค๋ฅผ Driver trait์œผ๋กœ ๋ฐ”๊พธ์—ˆ์Šต๋‹ˆ๋‹ค. 

๋”ฐ๋ผ์„œ Driver์˜ ํŠน์„ฑ์„ ๋งŒ์กฑํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค (1. ์˜ค๋ฅ˜ ๋ฐฉ์ถœ x, 2. ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰, 3. ๋ถ€์ž‘์šฉ์„ ๊ณต์œ  ex) share )

asDriver()๋Š” ์•„๋ž˜์™€ ๊ฐ™์€ ํšจ๊ณผ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

let safeSequence = xs
  .observeOn(MainScheduler.instance)        // observe events on main scheduler
  .catchErrorJustReturn(onErrorJustReturn)  // can't error out
  .share(replay: 1, scope: .whileConnected) // side effects sharing

return Driver(raw: safeSequence)            // wrap it up

 

๋งˆ์ง€๋ง‰์œผ๋กœ 

.drive(resultCount.rx.text)

resultCount๋ฅผ bind ํ•˜๋Š” ๋Œ€์‹  drive ํ•ด์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

bind(to: )์™€ ๊ฐ™์€ ํšจ๊ณผ๋ฅผ ๊ฐ€์ง€์ง€๋งŒ Driver trait์˜ ํŠน์„ฑ์„ ๋งŒ์กฑํ•˜๊ฒŒ๋” ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

 

Driver์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค. bind์™€ ๊ฐ™์€ ์—ญํ• ์„ ํ•˜์ง€๋งŒ Driver trait์„ ๋ณด์žฅํ•œ๋‹ค๋Š” ์ ์—์„œ UI ๋ฐ”์ธ๋”ฉ์„ ํ• ๋•Œ ๋งค์šฐ ์œ ์šฉํ•  ๊ฒƒ ๊ฐ™๊ตฐ์š” trait์˜ ์œ ์šฉํ•จ์„ ์ ์  ๋” ์•Œ์•„๊ฐ€๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. 

 

๋‹ค์Œ ๊ธ€์—์„œ๋Š” 

๋‚จ์€ ControlProperty, ControlEvent, Signal trait์— ๋Œ€ํ•ด ๊ณต๋ถ€ ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค ๊ทธ๋Ÿผ ์•ˆ๋‡ฝ 

๋Œ“๊ธ€