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

๐ŸŽ iOS/RxSwift

[RxSwift] Relay๋ž€?

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

์ €๋ฒˆ Rx ๊ฒŒ์‹œ๊ธ€์ด์—ˆ๋˜ Subject์— ์ด์–ด Relay์— ๋Œ€ํ•ด์„œ๋„ ํ•œ ๋ฒˆ ์ •๋ฆฌํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ฒˆ์—๋„ RxSwift: Reactive Programming with Swift ์ฑ…์„ ์ฐธ๊ณ ํ•ด์„œ ์ •๋ฆฌ๋ฅผ ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!

Subject์— ๋Œ€ํ•ด ์ž˜ ๋ชจ๋ฅด์‹œ๊ฒ ๋‹ค๋ฉด ์•„๋ž˜์˜ ๊ฒŒ์‹œ๊ธ€์„ ๋จผ์ € ์ฝ๊ณ  ์˜ค์‹œ๋Š” ๊ฒƒ์„ ์ถ”์ฒœ๋“œ๋ฆฝ๋‹ˆ๋‹ค.

 

[RxSwift] Subject๋ž€?

์•ˆ๋…•ํ•˜์„ธ์š” ์ œ์ธ์ž…๋‹ˆ๋‹ค : ) ์ฐธ ์˜ค๋žœ๋งŒ์— RxSwift ๊ฒŒ์‹œ๊ธ€ ์“ฐ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.. ๋ฌธ ๋‹ซ์€๊ฑฐ ์•„๋‹ˆ๊ตฌ์š” ์ •์ƒ์˜์—…ํ•˜๋ ค๊ตฌ์š”.. ๋„ค.. ์‚ฌ์‹ค ๊ทธ๋™์•ˆ RxSwift ๊ณต๋ถ€๋ฅผ ์•„์˜ˆ ์•ˆํ•œ ๊ฒƒ์€ ์•„๋‹Œ๋ฐ์š”.. ๋ญ”๊ฐ€ ๊ฒŒ์‹œ๊ธ€์„ ์“ธ ๋•Œ

janechoi.tistory.com

Relay๋ž€?

Relay๋Š” Subject์˜ Wrapper ํด๋ž˜์Šค๋กœ, RxCocoa ๋‚ด๋ถ€์— import ๋˜์–ด์žˆ์Šต๋‹ˆ๋‹ค!

๊ทธ๋Ÿฌ๋ฉด ์ด์ œ ๊ตฌ์กฐ์ ์ธ ๋ถ€๋ถ„์ด ๊ถ๊ธˆํ•ด์ง€๋Š”๋ฐ์š”.. ๊ทธ๋ž˜์„œ ReactiveX์˜ Github๋ฌธ์„œ๋ฅผ ํ™•์ธํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค..!

RxSwift ํŒŒํŠธ ์„ค๋ช…์„ ์ฐพ์•„๋ณด๋ฉด,  <understand the structure> ์ด๋ผ๋Š” ํŒŒํŠธ์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ค๋ช…์ด ๋‚˜์™€์žˆ์Šต๋‹ˆ๋‹ค.

 

RxSwift์˜ ๊ตฌ์กฐ

 

RxSwift์˜ ํ•ต์‹ฌ ๋‹จ์œ„๋Š” RxSwift ๊ทธ ์ž์‹ ์ด๋ฉฐ, UI ์ž‘์—…, ํ…Œ์ŠคํŠธ ๋“ฑ์„ ์œ„ํ•ด ๋‹ค๋ฅธ ์ข…์†์„ฑ์ด ์ถ”๊ฐ€๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์œ„์˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์„œ๋กœ ์˜์กดํ•˜๋Š” ๋‹ค์„ฏ ๊ฐœ์˜ ๊ฐœ๋ณ„ ๊ตฌ์„ฑ ์š”์†Œ๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

 

  • RxSwift: RxSwift์˜ ํ•ต์‹ฌ์œผ๋กœ, (๋Œ€๋ถ€๋ถ„) ReactiveX์— ์˜ํ•ด ์ •์˜๋œ Rx ํ‘œ์ค€์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ ์ข…์†์„ฑ์€ ๊ฐ€์ง€์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • RxCocoa: ์ผ๋ฐ˜์ ์ธ iOS/macOS/watchOS & tvOS ์•ฑ ๊ฐœ๋ฐœ์„ ์œ„ํ•ด Shared Sequences, Traits ๋“ฑ๊ณผ ๊ฐ™์€ ์ฝ”์ฝ”์•„ ๊ณ ์œ ์˜ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” RxSwift์™€ RxRelay์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค.
  • RxRelay: PublishRelay, BehaviorRelay, ReplayRelay ๋ผ๋Š” ์„ธ ๊ฐ€์ง€ Subject์— ๋Œ€ํ•œ wrapper ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” RxSwift์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค.
  • RxTest & RxBlocking: Rx ๊ธฐ๋ฐ˜ ์‹œ์Šคํ…œ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” RxSwift์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค.

๊ทธ๋ฆผ์œผ๋กœ ๋ณด๋‹ˆ RxSwift์˜ ๊ตฌ์กฐ์™€ ์˜์กด๊ด€๊ณ„๊ฐ€ ํ•œ๋ˆˆ์— ์ž˜ ํŒŒ์•…๋˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค!

RxRelay๋Š” RxCocoa์— ๋‚ด์žฅ๋œ Subject์˜ Wrapper ํด๋ž˜์Šค๋ผ๊ณ  ํ–ˆ์œผ๋‹ˆ UIEvent ์ฒ˜๋ฆฌ์—์„œ Subject์™€ ๊ฐ™์€ ์—ญํ• ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํด๋ž˜์Šค์ผ ๊ฒƒ ๊ฐ™๋‹ค๋Š” ์œ ์ถ”๋ฅผ ํ•ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์€๋ฐ์š”, Subject์™€์˜ ์ฐจ์ด์ ์— ๋Œ€ํ•ด ๊ฐ„๋žตํ•˜๊ฒŒ ์‚ดํŽด๋ณด๊ณ  ์ฝ”๋“œ๋กœ ํ•˜๋‚˜์”ฉ ๋ณด๋ฉด์„œ ๋” ์ž์„ธํžˆ ์•Œ์•„๋ด…์‹œ๋‹ค. (์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ๋Š” raywenderlich ์ฑ…์„ ๋ฒˆ์—ญํ•˜๋ฉด์„œ ์ •๋ฆฌํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค!)

 

observable, subject์™€ ๋‹ค๋ฅด๊ฒŒ relay์—๋Š” accept(_:)๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ’์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, relay์—์„œ๋Š” onNext(_:)๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค! ์™œ๋ƒํ•˜๋ฉด, relay๋Š” ๊ฐ’์„ acceptํ•˜๋Š” ๊ฒƒ๋งŒ ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ์ด๋Š” relay์— error๋‚˜ completed ์ด๋ฒคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๋ง๊ณผ ๋™์ผํ•˜๋ฉฐ, ์—ฌ๊ธฐ์„œ ์•Œ ์ˆ˜ ์žˆ๋Š” subject์™€์˜ ์ฐจ์ด์ ์€ relay๋Š” ์ข…๋ฃŒ๋˜์ง€ ์•Š์Œ์„ ๋ณด์žฅํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

PublishRelay

// 1
let relay = PublishRelay<String>()
let disposeBag = DisposeBag()

// 2
relay.accept("Knock knock, anyone home?")

// 3
relay
    .subscribe(onNext: {
        print($0)
    })
    .disposed(by: disposeBag)

// 4
relay.accept("1") // Print: 1

 

1๋ฒˆ ์ฃผ์„๋ถ€ํ„ฐ๋ณด๋ฉด, 1์—์„œ relay์˜ ์ƒ์„ฑ๋ฐฉ๋ฒ•์€ subject์™€ ๋‹ค๋ฅธ์ ์ด ์—†์Šต๋‹ˆ๋‹ค! 

๊ทธ๋Ÿฌ๋‚˜ 2๋ฒˆ์—์„œ relay์— ๊ฐ’์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ณผ์ •์„ ๋ณด๋ฉด onNext(_:)๊ฐ€ ์•„๋‹Œ accept(_:) ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2๋ฒˆ์—์„œ ์ƒˆ๋กœ์šด ๊ฐ’์„ ์ถ”๊ฐ€ํ•ด์ฃผ์—ˆ์ง€๋งŒ ์•„์ง subscriber๊ฐ€ ์—†์œผ๋‹ˆ ์•„๋ฌด ๊ฐ’๋„ ๋ฐฉ์ถœ๋˜์ง€ ์•Š๊ฒ ์ฃ ?

3๋ฒˆ์—์„œ subscriber๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๊ณ  ์ƒ์„ฑ ์ดํ›„ 4๋ฒˆ์—์„œ ์ƒˆ๋กœ์šด ๊ฐ’์ธ "1"์„ ์ถ”๊ฐ€ํ•˜๊ฒŒ ๋˜๋ฉด ์ด ๊ฐ’์ด ํ”„๋ฆฐํŠธ ๋ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ๊นŒ์ง€๋Š” PublishSubject์™€ ๋‹ค๋ฅธ ๊ฒƒ์ด ์—†์Šต๋‹ˆ๋‹ค! 

๊ทธ๋Ÿฌ๋‚˜, relay์—๋Š” error ํ˜น์€ completed ์ด๋ฒคํŠธ๋ฅผ ์ถ”๊ฐ€ํ•  ๋ฐฉ๋ฒ•์ด ์—†์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด ์ปดํŒŒ์ผ๋Ÿฌ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

relay.accept(MyError.anError)
relay.onCompleted()

 

์ •๋ฆฌํ•ด๋ณด๋ฉด, PublishRelay๋Š” ์ƒˆ๋กœ์šด ๊ฐ’์„ onNext(_:) ๊ฐ€ ์•„๋‹ˆ๋ผ accept(_:)๋ฅผ ์ด์šฉํ•ด์„œ ์ถ”๊ฐ€ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์ œ์™ธํ•˜๊ณ ๋Š” PublishSubject์™€ ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋ฉฐ ์ข…๋ฃŒ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

BehaviorRelay

BehaviorRelay๋„ ๋‹น์—ฐํžˆ completed๋‚˜ error ์ด๋ฒคํŠธ๋กœ ์ข…๋ฃŒ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ ์€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.

PublishRelay์™€ ๋‹ค๋ฅธ์ ์€ BehaviorRelay๋Š” BehaviorSubject๋ฅผ ๋ž˜ํ•‘ํ•œ ํด๋ž˜์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ์ดˆ๊ธฐ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์ƒ์„ฑ๋˜๋ฉฐ ์ƒˆ๋กœ์šด subscriber์—๊ฒŒ ์ดˆ๊ธฐ๊ฐ’ ๋˜๋Š” ์ตœ์‹ ๊ฐ’์„ ๋ฐฉ์ถœํ•œ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋˜ํ•œ, BehaviorRelay์˜ ํŠน๋ณ„ํ•œ ๊ธฐ๋Šฅ์€ ์–ธ์ œ๋“  ํ˜„์žฌ ๊ฐ’์„ ์š”์ฒญํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์ธ๋ฐ, ์ด ๊ธฐ๋Šฅ์€ ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋“œ๋กœ ์‚ดํŽด๋ด…์‹œ๋‹ค!

 

// 1
let relay = BehaviorRelay(value: "Initial value")
let disposeBag = DisposeBag()
        
// 2
relay.accept("New initial value")
        
// 3
relay
    .subscribe {
        print(label: "1)", event: $0) // Prints: 1) New initial value
    }
    .disposed(by: disposeBag)

 

์œ„์˜ ์ฝ”๋“œ๋Š” ์ง๊ด€์ ์œผ๋กœ ์ดํ•ด๊ฐ€ ๋  ๊ฒƒ ๊ฐ™์€๋ฐ์š”!

๊ฐ„๋žตํ•˜๊ฒŒ ์„ค๋ช…ํ•ด๋ณด์ž๋ฉด, ์ดˆ๊ธฐ๊ฐ’์„ ๊ฐ€์ง€๋Š” BehaviorRelay๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ด์— "New initial value"๋ผ๋Š” ๊ฐ’์„ accept(_:)๋ฅผ ์ด์šฉํ•ด ์ถ”๊ฐ€ํ•ด์ค๋‹ˆ๋‹ค. ์ดํ›„์— subscriber๊ฐ€ ์ƒ์„ฑ๋˜์ง€๋งŒ BehaviorRelay๋Š” ์ƒˆ๋กœ์šด subscriber์—๊ฒŒ ์ตœ์‹ ์˜ ๊ฐ’์„ ๋ฐฉ์ถœํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ตฌ๋… ์ด์ „์— ์ถ”๊ฐ€๋œ ๊ฐ’์ด ๋ฐฉ์ถœ๋˜์–ด "1) New initial value" ๋ผ๋Š” ๊ฐ’์ด ์ฐํžˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์ด์ œ ์•„๋ž˜์˜ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด๋ด…์‹œ๋‹ค!

 

// 1
relay.accept("1")

// 2
relay
  .subscribe {
    print(label: "2)", event: $0)
  }
  .disposed(by: disposeBag)
  
// 3
relay.accept("2")
/* Prints
1) 1
2) 1
1) 2
2) 2
*/

 

์œ„์—์„œ๋ถ€ํ„ฐ ๋ณด๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์€ ํ๋ฆ„์œผ๋กœ ์ƒˆ๋กœ์šด ๊ฐ’๊ณผ ๊ตฌ๋…์ด ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

1. relay์— ์ƒˆ๋กœ์šด ๊ฐ’ ์ถ”๊ฐ€

2. relay์— ์ƒˆ๋กœ์šด ๊ตฌ๋… ์ถ”๊ฐ€

3. relay์— ๋˜ ๋‹ค๋ฅธ ์ƒˆ๋กœ์šด ๊ฐ’ ์ถ”๊ฐ€

 

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์ด๋ฏธ ๊ตฌ๋…ํ•˜๊ณ  ์žˆ๋˜ 1)์ด relay์— ์ถ”๊ฐ€๋œ "1" ์ด๋ผ๋Š” ๊ฐ’์„ ๋ฐ›๊ฒŒ ๋˜์–ด "1) 1" ์ด ๋จผ์ € ํ”„๋ฆฐํŠธ ๋˜๊ณ , 2)๋ผ๋Š” ๊ตฌ๋…์ด ์ถ”๊ฐ€๋  ๋•Œ ์ตœ์‹ ์˜ ๊ฐ’์„ ๋ฐ›๊ฒŒ ๋˜๋‹ˆ ๊ทธ ๋‹ค์Œ์œผ๋กœ "2) 1"์ด ํ”„๋ฆฐํŠธ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ ๋‹ค์Œ, 3๋ฒˆ ์ฃผ์„์—์„œ "2" ๋ผ๋Š” ๊ฐ’์ด ์ถ”๊ฐ€๋˜๋ฉด 1), 2) ๊ตฌ๋… ๋ชจ๋‘ ๊ทธ ๊ฐ’์„ ๋ฐ›๊ฒŒ ๋˜๋‹ˆ "1) 2"์™€ "2) 2" ๊ฐ€ ์ˆœ์„œ๋Œ€๋กœ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

 

BehaviorRelay์—๋Š” ์–ธ์ œ๋“  ํ˜„์žฌ์˜ ๊ฐ’์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ํ•œ ๊ฐ€์ง€ ํŠน๋ณ„ํ•œ ๊ธฐ๋Šฅ์ด ์žˆ๋‹ค๊ณ  ํ–ˆ์ฃ ?

์•„๋ž˜์™€ ๊ฐ™์ด relay.value ๋กœ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ณ , ์œ„์˜ ์ฝ”๋“œ์—์„œ ๊ฐ€์žฅ ์ตœ๊ทผ์— ์ถ”๊ฐ€๋œ "2" ๋ผ๋Š” ๊ฐ’์ด ์ฝ˜์†”์— ์ถœ๋ ฅ๋˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

 

print(relay.value) // Print: 2

ReplayRelay

ReplayRelay๋Š” RxSwift 6์—์„œ ์ƒˆ๋กญ๊ฒŒ ์ถ”๊ฐ€๋œ ์—ฐ์‚ฐ์ž ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.

๋‹น์—ฐํžˆ ReplaySubject์˜ Wrapper ํด๋ž˜์Šค์ด๋ฉฐ, ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์ •ํ•ด์ง„ ๋ฒ„ํผ ์‚ฌ์ด์ฆˆ๋ฅผ ๊ฐ€์ง„ ์ฑ„๋กœ ์ดˆ๊ธฐํ™”๋˜๊ณ  ๋ฒ„ํผ ์‚ฌ์ด์ฆˆ ๋งŒํผ์˜ ๊ฐ’๋“ค์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด subscriber์—๊ฒŒ ๋ฐฉ์ถœํ•œ๋‹ค๋Š” ๋™์ผํ•œ ํŠน์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค๋ฅธ relay๋“ค๊ณผ ๊ฐ™์ด error๋‚˜ completed๋กœ ์ข…๋ฃŒ๋˜์ง€ ์•Š์œผ๋ฉฐ accept(_:)๋กœ ์ƒˆ๋กœ์šด ๊ฐ’๋“ค์„ ์ถ”๊ฐ€ํ•œ๋‹ค๋Š” ๊ฒƒ์„ ์ œ์™ธํ•˜๋ฉด ReplaySubject์™€ ๋™์ผํ•˜๋‹ˆ ๋”ฐ๋กœ ์˜ˆ์‹œ ์ฝ”๋“œ๋Š” ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค!