BehaviorSubject

๐Ÿช„ BehaviorSubject โ€” ์ตœ์‹  ๊ฐ’ ๋ณด์กด Subject

ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ: Last Emitted Value ยท Initial Seed ยท Hot Stream

BehaviorSubject<Element>๋Š” ํ•ญ์ƒ ์ตœ์‹  ๊ฐ’์„ 1๊ฐœ ์ €์žฅํ•˜๊ณ , ์ƒˆ ๊ตฌ๋…์ž์—๊ฒŒ ์ฆ‰์‹œ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ์ดˆ๊ธฐ๊ฐ’(seed)์„ ์š”๊ตฌํ•˜๋ฏ€๋กœ ์ƒํƒœ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ด์•ผ ํ•˜๋Š” UX(ํ† ๊ธ€, ํผ ์ž…๋ ฅ ๋“ฑ)์— ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.


1๏ธโƒฃ ํŠน์ง• ์š”์•ฝ

ํ•ญ๋ชฉ
๋‚ด์šฉ

์ดˆ๊ธฐ๊ฐ’

โœ”๏ธ ํ•„์ˆ˜ (init(value:))

์บ์‹œ ํฌ๊ธฐ

1 (์ตœ์‹  ๊ฐ’)

๊ตฌ๋… ์‹œ ์ „๋‹ฌ

์ตœ์‹  ๊ฐ’ ์ฆ‰์‹œ ๋ฐœํ–‰

์ŠคํŠธ๋ฆผ ์ข…๋ฃŒ

.onCompleted() ๋˜๋Š” .onError() ์ „ํŒŒ ํ›„ ์บ์‹œ ์ œ๊ฑฐ


2๏ธโƒฃ ๊ธฐ๋ณธ ์‚ฌ์šฉ๋ฒ•

let subject = BehaviorSubject(value: "๐Ÿšฆ Red")
let bag = DisposeBag()

// ์ฒซ ๊ตฌ๋…์ž โ†’ ์ดˆ๊ธฐ๊ฐ’ ์ „๋‹ฌ
subject
    .subscribe(onNext: { print("1๏ธโƒฃ",
$0) })
    .disposed(by: bag)

subject.onNext("๐ŸŸก Yellow")

// ๋‘ ๋ฒˆ์งธ ๊ตฌ๋…์ž โ†’ ์ตœ์‹ ๊ฐ’ Yellow ์ „๋‹ฌ
subject
    .subscribe(onNext: { print("2๏ธโƒฃ",
$0) })
    .disposed(by: bag)

subject.onNext("๐ŸŸข Green")

์ถœ๋ ฅ

1๏ธโƒฃ ๐Ÿšฆ Red
1๏ธโƒฃ ๐ŸŸก Yellow
2๏ธโƒฃ ๐ŸŸก Yellow
1๏ธโƒฃ ๐ŸŸข Green
2๏ธโƒฃ ๐ŸŸข Green

3๏ธโƒฃ ViewModel ์ƒํƒœ ๋ณด์กด ์˜ˆ์ œ โ€” ๋กœ๊ทธ์ธ ๋ฒ„ํŠผ ํ™œ์„ฑํ™”

struct Input {
    let email: Observable<String>
    let password: Observable<String>
}

struct Output {
    let isLoginEnabled: Observable<Bool>
}

func transform(input: Input) -> Output {
    let isValid = Observable
        .combineLatest(input.email, input.password)
        .map { email, pw in email.contains("@") && pw.count >= 6 }

    let enabledSubject = BehaviorSubject(value: false)

    isValid
        .bind(to: enabledSubject)
        .disposed(by: bag)

    return Output(isLoginEnabled: enabledSubject.asObservable())
}
  • BehaviorSubject๊ฐ€ ์ตœ์‹  ์œ ํšจ์„ฑ ๊ฒฐ๊ณผ๋ฅผ ์บ์‹ฑ โ†’ ํ™”๋ฉด ์žฌ์ง„์ž… ์‹œ ์ฆ‰์‹œ ๋ฐ˜์˜.


4๏ธโƒฃ vs PublishSubject ๋น„๊ต

ํ•ญ๋ชฉ

PublishSubject

BehaviorSubject

์ดˆ๊ธฐ๊ฐ’

์—†์Œ

ํ•„์ˆ˜

์บ์‹œ

0

1

๊ตฌ๋… ์งํ›„ ๊ฐ’

โŒ

์ตœ์‹  1๊ฐœ

์‚ฌ์šฉ ์‚ฌ๋ก€

๋‹จ์ˆœ ์ด๋ฒคํŠธ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ

UI ์ƒํƒœ, ์„ค์ • ๊ฐ’, ๋ฐ์ดํ„ฐ ์บ์‹œ


5๏ธโƒฃ ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ & ์—๋Ÿฌ ์ฒ˜๋ฆฌ

// ํ˜„์žฌ ๊ฐ’ ์ฝ๊ธฐ
if let current = try? subject.value() {
    print("Current ->", current)
}

// ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ value() ํ˜ธ์ถœ์€ throw
subject.onError(MyError.invalid)

// try? subject.value() -> nil, ๊ตฌ๋… ์‹œ .error ์ฆ‰์‹œ ์ „๋‹ฌ

value()๋Š” Subject๊ฐ€ ์—๋Ÿฌ/์™„๋ฃŒ ์ƒํƒœ๋ฉด throw ํ•˜๋ฏ€๋กœ, ์˜ต์…”๋„ try? ํŒจํ„ด์ด ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.


6๏ธโƒฃ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ & ์ฃผ์˜

  1. Strong reference: BehaviorSubject๋Š” ๋งˆ์ง€๋ง‰ ๊ฐ’ ๋ณด์œ  โ†’ ํฌ๊ธฐ๊ฐ€ ํฐ ๋ชจ๋ธ/์ด๋ฏธ์ง€ ์ €์žฅ ์ฃผ์˜.

  2. State explosion: ๋„ˆ๋ฌด ๋งŽ์€ BehaviorSubject โ†’ UI ์ƒํƒœ ๋ถ„์‚ฐ, Relay ๋˜๋Š” combineLatest๋กœ ๊ตฌ์กฐํ™”.

  3. Thread safety: ๋‹ค์ค‘ ์“ฐ๋ ˆ๋“œ onNext ์‹œ Data Race ๊ฐ€๋Šฅ โ†’ Serial Scheduler, Actor ํ™œ์šฉ.


7๏ธโƒฃ Mini Quiz

  1. BehaviorSubject๋ฅผ .onCompleted() ํ›„ ๋‹ค์‹œ onNext ํ˜ธ์ถœํ•˜๋ฉด?

  2. ์บ์‹œ๋œ ์ตœ์‹  ๊ฐ’์ด ํฌ๊ธฐ๊ฐ€ ํฐ ์ด๋ฏธ์ง€๋ผ๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋Œ€์‘ ๋ฐฉ๋ฒ• ๋‘ ๊ฐ€์ง€๋Š”?

  3. Combine ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ๋น„์Šทํ•œ ์ปจ์…‰์€ ๋ฌด์—‡์ธ๊ฐ€?

์ •๋‹ต
  1. ๋ถˆ๊ฐ€ โ€” ์ŠคํŠธ๋ฆผ์€ Completed ์ƒํƒœ๋กœ, ์ดํ›„ ์ด๋ฒคํŠธ๋Š” ๋ฌด์‹œ๋ฉ๋‹ˆ๋‹ค. ์ƒˆ Subject๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

  2. ๋Œ€์‘ ๋ฐฉ๋ฒ•

    1. ์ด๋ฏธ์ง€์˜ URL ๋˜๋Š” ์‹๋ณ„์ž๋งŒ ๋ณด๊ด€ํ•˜๊ณ  ์‹ค์ œ ๋ฐ์ดํ„ฐ๋Š” ์บ์‹œ ๋ ˆ์ด์–ด๋กœ ๋ถ„๋ฆฌ.

    2. BehaviorRelay๋กœ ๊ต์ฒด ํ›„ ๊ฐ’ ํƒ€์ž…์„ WeakWrapper(์ฐธ์กฐ ํƒ€์ž…)๋กœ ๋ž˜ํ•‘

  3. Combine์˜ **CurrentValueSubject**๊ฐ€ BehaviorSubject์™€ ๋™์ผํ•˜๊ฒŒ ์ตœ์‹  ๊ฐ’ 1๊ฐœ๋ฅผ ์ €์žฅ ํ›„ ์ƒˆ ๊ตฌ๋…์ž์—๊ฒŒ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค.


๋‹ค์Œ ๋ฌธ์„œ โ–ถ๏ธ ReplaySubject ๋กœ ๋„˜์–ด๊ฐ€ ๋‹ค์ค‘ ๊ฐ’ ์บ์‹œ ์ „๋žต์„ ์‚ดํŽด๋ด…๋‹ˆ๋‹ค. ๐Ÿš€

Last updated