[TIL] ๐ŸŒผ24/05/10๐ŸŒผ#MutableStateFlow value vs emit vs update

0

TIL

๋ชฉ๋ก ๋ณด๊ธฐ
98/104
post-thumbnail
post-custom-banner

[TIL] ๐ŸŒผ24/05/10๐ŸŒผ#MutableStateFlow value vs emit vs update

MutableStateFlow value vs emit vs update

๐Ÿ“Œ์ฐธ๊ณ ์ž๋ฃŒ

๐Ÿ“Œ๋„์›€๋ ๋งŒํ•œ ์ง€๋‚œ ํฌ์ŠคํŒ…

  • MutableStateFlow์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ•
    • MutableStateFlow.value = newValue
    • MutableStateFlow.emit(newValue)
    • MutableStateFlow.update{ newValue }

value ํ”„๋กœํผํ‹ฐ๋กœ ๊ฐ’ ๋ณ€๊ฒฝํ•˜๊ธฐ

  • MutableStateFlow์˜ read-write ํ”„๋กœํผํ‹ฐ
    • flow๋ฅผ subscribeํ•˜์ง€ ์•Š์•„๋„ MutableStateFlow์˜ ๊ฐ’ set/get๊ฐ€๋Šฅ
    • readํ•˜๋Š” ๊ฒฝ์šฐ, get์„ ํ˜ธ์ถœํ•˜๋Š” ์‹œ์ ์˜ snapshot ๋ฐ˜ํ™˜
  • thread-safe

emit ํ•จ์ˆ˜๋กœ ๊ฐ’ ๋ณ€๊ฒฝํ•˜๊ธฐ

  • MutableStateFlow ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” suspend function
    -> coroutine/coroutine context ๋‚ด์—์„œ ํ˜ธ์ถœํ•ด์•ผ
  • flow์˜ ๋ฒ„ํผ๊ฐ€ full์ธ ๊ฒฝ์šฐ, buffer์— ์ž๋ฆฌ๊ฐ€ ์ƒ๊ธธ ๋•Œ๊นŒ์ง€ emit ํ•จ์ˆ˜ suspend ๋จ!
  • ์ฐธ๊ณ ) tryEmit ํ•จ์ˆ˜
    • MutableStateFlow์— emit์„ ์‹œ๋„ํ•˜๋Š” ํ•จ์ˆ˜
      -> emit ์„ฑ๊ณต ์‹œ true ๋ฐ˜ํ™˜, ์‹คํŒจ ์‹œ false ๋ฐ˜ํ™˜
    • thread-safe

update ํ•จ์ˆ˜๋กœ ๊ฐ’ ๋ณ€๊ฒฝํ•˜๊ธฐ

  • MutableStateFlow์˜ ๊ฐ’์„ atomicํ•˜๊ฒŒ ๋ณ€๊ฒฝํ•˜๊ธฐ ์œ„ํ•œ ํ™•์žฅํ•จ์ˆ˜
    • MutableStateFlow์˜ ๊ฐ’์ด ์ œ๋Œ€๋กœ ๊ฐฑ์‹ ๋˜์—ˆ๋Š”์ง€ busy loop์„ ๋Œ๋ฉฐ ํ™•์ธ
public inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
    while (true) {
        val prevValue = value
        val nextValue = function(prevValue)
        if (compareAndSet(prevValue, nextValue)) {
            return
        }
    }
}
  • updateํ•จ์ˆ˜๊ฐ€ ํ•„์š”ํ•œ ์ด์œ ?
    value ํ”„๋กœํผํ‹ฐ๋กœ flow์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ๋•Œ set ์—ฐ์‚ฐ ์ž์ฒด๋Š” thread-safeํ•˜์ง€๋งŒ, ์ฃผ์˜ํ•ด์•ผ ํ•  race condition์ด ์žˆ์Œ
// Launch A
viewModelScope.launch(Dispatchers.IO) {
    _viewState.value = _viewState.value.copy(doneButtonEnabled = true)
}
// Launch B
viewModelScope.launch(Dispatchers.Default) {
    _viewState.value = _viewState.value.copy(title = "New Title")
}
  • ์ดˆ๊ธฐ ์ƒํƒœ
    _viewState: title = "Old Title", doneButtonEnabled = false
  • Launch A ์‹คํ–‰ ์‹œ์ž‘
    _viewState: title = "Old Title", doneButtonEnabled = false
    _viewState.value.copy ์ƒํƒœ: title = "Old Title", doneButtonEnabled = true
  • Launch A ์ผ์‹œ์ •์ง€, Launch B ์‹คํ–‰ ์‹œ์ž‘
    _viewState: title = "Old Title", doneButtonEnabled = false
    _viewState.value.copy ์ƒํƒœ: title = "New Title", doneButtonEnabled = false
  • Launch B ์ผ์‹œ์ •์ง€, Launch A ์‹คํ–‰ ์žฌ๊ฐœ
    _viewState: title = "Old Title", doneButtonEnabled = true
  • Launch B ์‹คํ–‰ ์žฌ๊ฐœ
    _viewState: title = "New Title", doneButtonEnabled = false
  • ์œ„์™€ ๊ฐ™์€ ์ผ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์œผ๋ ค๋ฉด, copy์™€ value set์„ amoticํ•˜๊ฒŒ ์‹คํ–‰ํ•ด์•ผ ํ•จ!
viewModelScope.launch(Dispatchers.IO) {
  _viewState.update { it.copy(doneButtonEnabled = true) }
}

viewModelScope.launch(Dispatchers.Default) {
  _viewState.update { it.copy(title = "New title") }
}
  • ์ฐธ๊ณ ) mutex lock์„ ์‚ฌ์šฉํ•˜์—ฌ atomicํ•˜๊ฒŒ ์‹คํ–‰๋˜๋„๋ก ์ง์ ‘ lock์„ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์Œ
val mutex = Mutex()

viewModelScope.launch(Dispatchers.IO) {
    mutex.withLock { 
        _viewState.value = _viewState.value.copy(doneButtonEnabled = true)
    }
}

viewModelScope.launch(Dispatchers.Default) {
    mutex.withLock { 
        _viewState.value = _viewState.value.copy(title = "New title")
    }
}
post-custom-banner

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