MutableStateFlow: value vs update

์ด์ง„์˜ยท2025๋…„ 8์›” 11์ผ
post-thumbnail

๐ŸŒŠ Android MutableStateFlow: value vs update ์™„๋ฒฝ ๊ฐ€์ด๋“œ


๐Ÿ’ฌ Android ๊ฐœ๋ฐœ์—์„œ ์ƒํƒœ ๊ด€๋ฆฌ๋Š” ํ•ต์‹ฌ ์š”์†Œ์ž…๋‹ˆ๋‹ค.
ํŠนํžˆ MutableStateFlow๋Š” Kotlin Coroutines์™€ ํ•จ๊ป˜ ์“ฐ์ด๋Š” ๊ฐ•๋ ฅํ•œ ์ƒํƒœ ๊ด€๋ฆฌ ๋„๊ตฌ๋กœ,
๋งŽ์€ ๊ฐœ๋ฐœ์ž๋“ค์ด ์„ ํ˜ธํ•˜๋Š” ํŒจํ„ด์ž…๋‹ˆ๋‹ค.


๐Ÿ”„ MutableStateFlow์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ๋•Œ๋Š”

  • โœ๏ธ value
  • ๐Ÿ›  update
    ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“š ๊ฐ ๋ฐฉ๋ฒ•์˜ ๋™์ž‘ ์›๋ฆฌ์™€ ์ฐจ์ด๋ฅผ ์ดํ•ดํ•˜๋ฉด
๋” ์•ˆ์ „ํ•˜๊ณ  ํšจ์œจ์ ์ธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ“– MutableStateFlow๋ž€?

๐Ÿ“Œ ์ •์˜: ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๋ฅผ ๊ฐ€์ง€๋ฉฐ, ์ƒํƒœ ๋ณ€ํ™”๋ฅผ ๊ด€์ฐฐํ•  ์ˆ˜ ์žˆ๋Š” Hot Stream

MutableStateFlow๋Š” StateFlow์˜ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ ๊ตฌํ˜„์ฒด๋กœ, ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํŠน์ง•์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค:

  • ํ˜„์žฌ ์ƒํƒœ ๋ณด์œ : ํ•ญ์ƒ ์ตœ์‹  ๊ฐ’์„ ๊ฐ€์ง€๊ณ  ์žˆ์Œ
  • Hot Stream: ๊ตฌ๋…์ž๊ฐ€ ์—†์–ด๋„ ๊ฐ’์„ ์œ ์ง€
  • ์ค‘๋ณต ์ œ๊ฑฐ: ๋™์ผํ•œ ๊ฐ’์ด ์—ฐ์†์œผ๋กœ ์„ค์ •๋˜๋ฉด ๋ฐฉ์ถœํ•˜์ง€ ์•Š์Œ
  • Thread-Safe: ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

๐Ÿ› ๏ธ MutableStateFlow ๊ฐ’ ๋ณ€๊ฒฝ ๋ฐฉ๋ฒ•

MutableStateFlow์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค:

val mutableStateFlow = MutableStateFlow(0)

// ๋ฐฉ๋ฒ• 1: value ์ง์ ‘ ํ• ๋‹น
mutableStateFlow.value = 10

// ๋ฐฉ๋ฒ• 2: update ํ•จ์ˆ˜ ์‚ฌ์šฉ
mutableStateFlow.update { currentValue ->
    currentValue + 1
}

๊ฐ๊ฐ์˜ ๋™์ž‘ ๋ฐฉ์‹๊ณผ ํŠน์ง•์„ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.


๐ŸŽฏ value ๋ฐฉ์‹: ์ง์ ‘ ํ• ๋‹น

๐Ÿ”จ ์‚ฌ์šฉ๋ฒ•

class CounterViewModel : ViewModel() {
    private val _counter = MutableStateFlow(0)
    val counter: StateFlow<Int> = _counter.asStateFlow()

    fun incrementDirectly() {
        _counter.value = _counter.value + 1
    }

    fun setCounter(newValue: Int) {
        _counter.value = newValue
    }
}

โšก value ๋ฐฉ์‹์˜ ๋™์ž‘ ์›๋ฆฌ

value ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค:

  1. ์ฆ‰์‹œ ํ• ๋‹น: ์ƒˆ๋กœ์šด ๊ฐ’์ด ๋ฐ”๋กœ ์„ค์ •๋จ
  2. ๋™๊ธฐ์  ์‹คํ–‰: ํ˜„์žฌ ์Šค๋ ˆ๋“œ์—์„œ ์ฆ‰์‹œ ์ฒ˜๋ฆฌ
  3. ๋‹จ์ˆœ ๋Œ€์ž…: ๋ณต์žกํ•œ ๋กœ์ง ์—†์ด ๊ฐ’๋งŒ ๊ต์ฒด
// ๋‚ด๋ถ€์ ์œผ๋กœ ์ด๋ ‡๊ฒŒ ๋™์ž‘
_counter.value = 5  // ์ฆ‰์‹œ 5๋กœ ์„ค์ •๋จ
println(_counter.value)  // 5 ์ถœ๋ ฅ

โš ๏ธ value ๋ฐฉ์‹์˜ ๋ฌธ์ œ์ 

๐Ÿ›ก๏ธ Thread-Safety ๋ณด์™„ ์„ค๋ช…
MutableStateFlow.value์˜ ์ฝ๊ธฐ/์“ฐ๊ธฐ ์ž์ฒด๋Š” ์›์ž์ ์ด์ง€๋งŒ,
value = value + 1์ฒ˜๋Ÿผ ์ฝ๊ธฐ โ†’ ๊ณ„์‚ฐ โ†’ ์“ฐ๊ธฐ๋กœ ์ด์–ด์ง€๋Š” ๋ณตํ•ฉ ์—ฐ์‚ฐ์€ ์›์ž์ ์ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ๋™์‹œ์— ๊ฐ™์€ ๊ฐ’์„ ์ฝ์–ด ๋ฎ์–ด์“ฐ๋Š” Race Condition์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” update { it + 1 }์ฒ˜๋Ÿผ ์›์ž์  CAS ๊ธฐ๋ฐ˜ ์—ฐ์‚ฐ์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

1. Race Condition ์œ„ํ—˜

class ProblematicCounter : ViewModel() {
    private val _counter = MutableStateFlow(0)
    
    fun incrementTwice() {
        viewModelScope.launch {
            _counter.value = _counter.value + 1
        }
        viewModelScope.launch {
            _counter.value = _counter.value + 1
        }
    }
}

โš ๏ธ ์ฃผ์˜: Race Condition ๊ฐ€๋Šฅ์„ฑ
๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ๋‘ ์ฝ”๋ฃจํ‹ด์ด ๋™์‹œ์— ์‹คํ–‰๋˜๋ฉด
๊ฐ™์€ ์ดˆ๊ธฐ๊ฐ’์„ ์ฝ๊ณ  ๋™์‹œ์— ์ฆ๊ฐ€์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

โœ… ์˜ˆ์ƒ ๊ฒฐ๊ณผ: +2 ์ฆ๊ฐ€
โŒ ์‹ค์ œ ๊ฒฐ๊ณผ: +1๋งŒ ์ฆ๊ฐ€ํ•  ์ˆ˜ ์žˆ์Œ

2. ๋ณตํ•ฉ ๊ฐ์ฒด ์ˆ˜์ • ์‹œ ๋ถˆ์•ˆ์ •์„ฑ

data class UserState(
    val name: String = "",
    val age: Int = 0,
    val isActive: Boolean = false
)

class UserViewModel : ViewModel() {
    private val _userState = MutableStateFlow(UserState())
    
    fun updateUserUnsafely() {
        val currentState = _userState.value
        _userState.value = currentState.copy(
            name = "์ƒˆ ์ด๋ฆ„",
            age = currentState.age + 1
        )
    }
}

โš ๏ธ ์ฃผ์˜: ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ฌธ์ œ
๋ณตํ•ฉ ๊ฐ์ฒด๋ฅผ ์ˆ˜์ •ํ•  ๋•Œ
currentState๋ฅผ ์ฝ๋Š” ์‹œ์ ๊ณผ ์ƒˆ ๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ์‹œ์  ์‚ฌ์ด์—
๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์–ด
๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ“Š Race Condition ์‹œ๋ฎฌ๋ ˆ์ด์…˜

@Test
fun testRaceCondition() = runTest {
    val counter = MutableStateFlow(0)
    
    val jobs = List(2) {
        launch {
            repeat(1000) {
                counter.value = counter.value + 1
            }
        }
    }
    
    jobs.joinAll()
    println("์ตœ์ข… ๊ฐ’: ${counter.value}")
}

โš ๏ธ ์ฃผ์˜: Race Condition ๊ฒฐ๊ณผ
์ด ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด
์˜ˆ์ƒ๊ฐ’ 2000๋ณด๋‹ค ์ž‘์€ ๊ฐ’์ด ๋‚˜์˜ฌ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค.


๐Ÿ”„ update ๋ฐฉ์‹: ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ

๐Ÿ”จ ์‚ฌ์šฉ๋ฒ•

class SafeCounterViewModel : ViewModel() {
    private val _counter = MutableStateFlow(0)
    val counter: StateFlow<Int> = _counter.asStateFlow()

    fun incrementSafely() {
        _counter.update { currentValue ->
            currentValue + 1
        }
    }

    fun multiplyByTwo() {
        _counter.update { it * 2 }
    }
}

๐Ÿ”ง update ํ•จ์ˆ˜์˜ ๋‚ด๋ถ€ ๊ตฌํ˜„

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 ๋ฐฉ์‹์˜ ๋™์ž‘ ์›๋ฆฌ

update ํ•จ์ˆ˜๋Š” Compare-And-Swap(CAS) ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค:

๐Ÿ”„ ๋‹จ๊ณ„๋ณ„ ์‹คํ–‰ ๊ณผ์ •

fun updateExample() {
    _counter.update { currentValue ->
        currentValue + 1
    }
}

// ๋‚ด๋ถ€ ๋™์ž‘:
// 1. while (true) ๋ฃจํ”„ ์‹œ์ž‘
// 2. val prevValue = value        // ํ˜„์žฌ ๊ฐ’ ์Šค๋ƒ…์ƒท
// 3. val nextValue = function(prevValue)  // ๋ณ€ํ™˜ ํ•จ์ˆ˜ ์‹คํ–‰
// 4. if (compareAndSet(prevValue, nextValue))  // CAS ์—ฐ์‚ฐ
//    - ์„ฑ๊ณต ์‹œ: return (๋ฃจํ”„ ์ข…๋ฃŒ)
//    - ์‹คํŒจ ์‹œ: 1๋‹จ๊ณ„๋กœ ๋Œ์•„๊ฐ€์„œ ์žฌ์‹œ๋„

๐Ÿง  CAS(Compare-And-Swap) ๋ฉ”์ปค๋‹ˆ์ฆ˜ ์ดํ•ด

// compareAndSet์˜ ๊ฐœ๋…์  ๋™์ž‘
fun compareAndSet(expected: T, newValue: T): Boolean {
    // ์›์ž์ ์œผ๋กœ(atomically) ๋‹ค์Œ์„ ์ˆ˜ํ–‰:
    if (currentValue == expected) {
        currentValue = newValue
        return true  // ์„ฑ๊ณต
    } else {
        return false  // ์‹คํŒจ (๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ’์„ ๋ณ€๊ฒฝํ•จ)
    }
}

๐Ÿ”„ ์žฌ์‹œ๋„ ๋ฉ”์ปค๋‹ˆ์ฆ˜์˜ ํ•„์š”์„ฑ

// ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ์˜ update ๋™์ž‘ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
Thread A: _counter.update { it + 1 }
Thread B: _counter.update { it * 2 }

์ดˆ๊ธฐ๊ฐ’: 10

  1. ๐Ÿงต Thread A โ€” ๐Ÿ“ฅ prevValue = 10
    ๐Ÿ”ง nextValue = 10 + 1 = 11
    ๐Ÿ” compareAndSet(10, 11) ์‹œ๋„...

  2. ๐Ÿงต Thread B (๋™์‹œ์— ์‹คํ–‰) โ€” ๐Ÿ“ฅ prevValue = 10
    ๐Ÿ”ง nextValue = 10 * 2 = 20
    โœ… compareAndSet(10, 20) ์„ฑ๊ณต โ†’ ๊ฐ’์ด 20์œผ๋กœ ๋ณ€๊ฒฝ

  3. ๐Ÿงต Thread A (๊ณ„์†) โ€” โŒ compareAndSet(10, 11) ์‹คํŒจ (ํ˜„์žฌ๊ฐ’์ด 20)
    โ™ป๏ธ while ๋ฃจํ”„๋กœ ์žฌ์‹œ๋„
    ๐Ÿ“ฅ prevValue = 20
    ๐Ÿ”ง nextValue = 20 + 1 = 21
    โœ… compareAndSet(20, 21) ์„ฑ๊ณต

๐Ÿ ์ตœ์ข… ๊ฒฐ๊ณผ: 21 (๋ชจ๋“  ์—ฐ์‚ฐ์ด ์•ˆ์ „ํ•˜๊ฒŒ ์ ์šฉ๋จ)


โœ… update ๋ฐฉ์‹์˜ ์žฅ์ 

1. Thread-Safe ๋ณด์žฅ

class ThreadSafeCounter : ViewModel() {
    private val _counter = MutableStateFlow(0)
    
    fun incrementTwiceSafely() {
        viewModelScope.launch {
            _counter.update { it + 1 }
        }
        viewModelScope.launch {
            _counter.update { it + 1 }
        }
    }
}

โœ… ์›์ž์  ์‹คํ–‰ ๋ณด์žฅ
์œ„ ์ฝ”๋“œ์—์„œ ๋‘ ๊ฐœ์˜ ์ฝ”๋ฃจํ‹ด์ด ๋™์‹œ์— ์‹คํ–‰๋˜์–ด๋„
๊ฐ update ํ˜ธ์ถœ์€ ์›์ž์ (atomic) ์œผ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

๐Ÿง  CAS ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ์ž๋™์œผ๋กœ ํ•ด๊ฒฐํ•˜์—ฌ
์ตœ์ข…์ ์œผ๋กœ ์ •ํ™•ํžˆ +2 ์ฆ๊ฐ€ํ•จ์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

2. ๋ณตํ•ฉ ๊ฐ์ฒด ์•ˆ์ „ํ•œ ์ˆ˜์ •

class SafeUserViewModel : ViewModel() {
    private val _userState = MutableStateFlow(UserState())
    
    fun updateUserSafely() {
        _userState.update { currentState ->
            currentState.copy(
                name = "์ƒˆ ์ด๋ฆ„",
                age = currentState.age + 1
            )
        }
    }
}

๐Ÿ”„ ํ•ญ์ƒ ์ตœ์‹  ์ƒํƒœ ๊ธฐ๋ฐ˜ ์—ฐ์‚ฐ
update ํ•จ์ˆ˜๋Š” ๋žŒ๋‹ค ๋‚ด๋ถ€์—์„œ ํ•ญ์ƒ ์ตœ์‹  ์ƒํƒœ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

โšก ๋งŒ์•ฝ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ์ค‘๊ฐ„์— ์ƒํƒœ๋ฅผ ๋ณ€๊ฒฝํ–ˆ๋‹ค๋ฉด,
CAS๊ฐ€ ์‹คํŒจํ•˜๊ณ  ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„ํ•˜์—ฌ
์ƒˆ๋กœ์šด ์ตœ์‹  ์ƒํƒœ๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์™€ ์•ˆ์ „ํ•œ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

3. ์žฌ์‹œ๋„ ๋กœ์ง ์ž๋™ ์ฒ˜๋ฆฌ

fun automaticRetryExample() {
    _counter.update { value ->
        value + 1
    }
}

๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ’์„ ๋ณ€๊ฒฝํ•ด๋„ update ํ•จ์ˆ˜๊ฐ€ ์ž๋™์œผ๋กœ ์žฌ์‹œ๋„ํ•˜์—ฌ ํ•ญ์ƒ ์ตœ์‹  ๊ฐ’์„ ๊ธฐ๋ฐ˜์œผ๋กœ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๋Š” ๋ณต์žกํ•œ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ๋กœ์ง์„ ์ง์ ‘ ๊ตฌํ˜„ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.


๐Ÿ” ์„ฑ๋Šฅ ๊ด€์ ์—์„œ์˜ ๋น„๊ต

โฑ๏ธ update ํ•จ์ˆ˜์˜ ์„ฑ๋Šฅ ํŠน์„ฑ

fun performanceAnalysis() {
    val counter = MutableStateFlow(0)
    
    // Case 1: ๊ฒฝํ•ฉ์ด ์—†๋Š” ๋‹จ์ผ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ
    counter.update { it + 1 }
    // โ†’ CAS ํ•œ ๋ฒˆ์— ์„ฑ๊ณต, ์ตœ์†Œ ์˜ค๋ฒ„ํ—ค๋“œ
    
    // Case 2: ๋†’์€ ๊ฒฝํ•ฉ์ด ์žˆ๋Š” ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ
    repeat(1000) {
        launch {
            counter.update { it + 1 }  // ์—ฌ๋Ÿฌ ๋ฒˆ ์žฌ์‹œ๋„ ๊ฐ€๋Šฅ
        }
    }
}

๐Ÿ“Š ์„ฑ๋Šฅ ๋น„๊ตํ‘œ

๐Ÿ“ ํ•ญ๋ชฉโœ๏ธ value ๋ฐฉ์‹๐Ÿ”„ update ๋ฐฉ์‹
โš™๏ธ ๋‹จ์ผ ์Šค๋ ˆ๋“œ ์„ฑ๋Šฅ์ฆ‰์‹œ ์‹คํ–‰CAS 1ํšŒ ์ˆ˜ํ–‰
๐Ÿค ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์„ฑ๋ŠฅโŒ Race Condition ๋ฐœ์ƒ ๊ฐ€๋ŠฅCAS + ์ž๋™ ์žฌ์‹œ๋„
๐Ÿ—‚ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น์ถ”๊ฐ€ ํ• ๋‹น ์—†์Œ๋žŒ๋‹ค ๊ฐ์ฒด ์ƒ์„ฑ
๐Ÿ–ฅ CPU ์‚ฌ์šฉ๋Ÿ‰์ตœ์†Œ์žฌ์‹œ๋„ ์‹œ ์ฆ๊ฐ€

๐Ÿ“Š value vs update ์ข…ํ•ฉ ๋น„๊ตํ‘œ

๐Ÿ“ ๊ตฌ๋ถ„โœ๏ธ value ๋ฐฉ์‹๐Ÿ”„ update ๋ฐฉ์‹
๐Ÿ’ก ์‚ฌ์šฉ ํŽธ์˜์„ฑ๊ฐ„๋‹จํ•œ ์ง์ ‘ ํ• ๋‹นํ•จ์ˆ˜ํ˜• ์ ‘๊ทผ
๐Ÿ›ก Thread SafetyโŒ Race Condition ์œ„ํ—˜โœ… CAS๋กœ ์•ˆ์ „ ๋ณด์žฅ
โš™๏ธ ์„ฑ๋Šฅ์ฆ‰์‹œ ์‹คํ–‰CAS ์˜ค๋ฒ„ํ—ค๋“œ ์•ฝ๊ฐ„ ์žˆ์Œ
๐Ÿ—‚ ๋ณตํ•ฉ ๊ฐ์ฒด ์ˆ˜์ •โš ๏ธ ๋ถˆ์•ˆ์ •โœ… ์•ˆ์ „ํ•œ ์›์ž์  ์ˆ˜์ •
๐Ÿงฉ ์กฐ๊ฑด๋ถ€ ๋กœ์งโŒ ์™ธ๋ถ€์—์„œ ์ฒ˜๋ฆฌ ํ•„์š”โœ… ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ์ฒ˜๋ฆฌ
๐Ÿ‘€ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์ง๊ด€์ ํ•จ์ˆ˜ํ˜• ์Šคํƒ€์ผ
โ™ป๏ธ ์žฌ์‹œ๋„ ์ฒ˜๋ฆฌโŒ ์ˆ˜๋™ ๊ตฌํ˜„ ํ•„์š”โœ… ์ž๋™ ์ฒ˜๋ฆฌ

๐ŸŽฏ ์‹ค์ œ ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค๋ณ„ ๊ถŒ์žฅ์‚ฌํ•ญ

โœ… value๋ฅผ ์‚ฌ์šฉํ•ด๋„ ์ข‹์€ ๊ฒฝ์šฐ

class SimpleViewModel : ViewModel() {
    private val _isLoading = MutableStateFlow(false)
    
    fun setLoading(loading: Boolean) {
        _isLoading.value = loading
    }
    
    fun resetCounter() {
        _counter.value = 0
    }
}

๐Ÿ’ก value ๋ฐฉ์‹์ด ์ ํ•ฉํ•œ ๊ฒฝ์šฐ
๋‹จ์ˆœํ•œ boolean ํ† ๊ธ€์ด๋‚˜ ์ดˆ๊ธฐํ™”์ฒ˜๋Ÿผ
๋ช…ํ™•ํ•œ ๊ฐ’ ์„ค์ •์ด ํ•„์š”ํ•˜๊ณ  ๋‹จ์ผ ์Šค๋ ˆ๋“œ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ์—๋Š”
value ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

โญ update๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ

class RecommendedViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    
    fun updateUiState() {
        _uiState.update { state ->
            state.copy(
                isLoading = false,
                data = newData,
                errorMessage = null
            )
        }
    }
    
    fun incrementCounter() {
        _counter.update { it + 1 }
    }
    
    fun incrementIfLessThan100() {
        _counter.update { current ->
            if (current < 100) current + 1 else current
        }
    }
}

๐Ÿ’ก update ๋ฐฉ์‹์ด ํ•„์ˆ˜์ ์ธ ๊ฒฝ์šฐ
๋ณตํ•ฉ ๊ฐ์ฒด ์ˆ˜์ •, ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ์˜ ์นด์šดํ„ฐ ์ฆ๊ฐ€,
์กฐ๊ฑด๋ถ€ ์—…๋ฐ์ดํŠธ ๋“ฑ์˜ ๊ฒฝ์šฐ์—๋Š”
๋ฐ˜๋“œ์‹œ update ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.


๐Ÿš€ ์‹ค์ „ ํ™œ์šฉ ์˜ˆ์ œ

๐Ÿ“ฑ ์‹ค์ œ ์•ฑ์—์„œ์˜ ํ™œ์šฉ

data class ShoppingCartState(
    val items: List<CartItem> = emptyList(),
    val totalPrice: Double = 0.0,
    val isLoading: Boolean = false,
    val errorMessage: String? = null
)

class ShoppingCartViewModel : ViewModel() {
    private val _cartState = MutableStateFlow(ShoppingCartState())
    val cartState: StateFlow<ShoppingCartState> = _cartState.asStateFlow()
    
    fun addItem(item: CartItem) {
        _cartState.update { state ->
            val newItems = state.items + item
            state.copy(
                items = newItems,
                totalPrice = calculateTotal(newItems),
                errorMessage = null
            )
        }
    }
    
    fun removeItem(itemId: String) {
        _cartState.update { state ->
            val newItems = state.items.filter { it.id != itemId }
            state.copy(
                items = newItems,
                totalPrice = calculateTotal(newItems)
            )
        }
    }
    
    fun updateQuantity(itemId: String, newQuantity: Int) {
        _cartState.update { state ->
            val newItems = state.items.map { item ->
                if (item.id == itemId) {
                    item.copy(quantity = newQuantity)
                } else {
                    item
                }
            }
            state.copy(
                items = newItems,
                totalPrice = calculateTotal(newItems)
            )
        }
    }
    
    fun setLoading(isLoading: Boolean) {
        _cartState.update { it.copy(isLoading = isLoading) }
    }
}

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ๊ณผ ํŒ

๐Ÿ”ฅ ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

// โŒ ๋น„ํšจ์œจ์ : ์—ฌ๋Ÿฌ ๋ฒˆ์˜ ๊ฐœ๋ณ„ ์—…๋ฐ์ดํŠธ
fun inefficientUpdate() {
    _state.update { it.copy(isLoading = true) }
    _state.update { it.copy(errorMessage = null) }
    _state.update { it.copy(data = newData) }
}

// โœ… ํšจ์œจ์ : ํ•œ ๋ฒˆ์— ๋ชจ๋“  ์†์„ฑ ์—…๋ฐ์ดํŠธ
fun efficientUpdate() {
    _state.update { state ->
        state.copy(
            isLoading = true,
            errorMessage = null,
            data = newData
        )
    }
}

๐Ÿง  ๋ฌดํ•œ ๋ฃจํ”„ ๋ฐฉ์ง€

// โš ๏ธ ์ฃผ์˜: update ํ•จ์ˆ˜ ๋‚ด์—์„œ ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ ํ”ผํ•˜๊ธฐ
fun avoidHeavyOperationInUpdate() {
    _state.update { state ->
        val heavyResult = performHeavyCalculation(state.data)
        state.copy(result = heavyResult)
    }
}

// โœ… ๊ถŒ์žฅ: ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ์€ ๋ฏธ๋ฆฌ ์ˆ˜ํ–‰
fun recommendedApproach() {
    val heavyResult = performHeavyCalculation(_state.value.data)
    
    _state.update { state ->
        state.copy(result = heavyResult)
    }
}

๐Ÿ”’ Deadlock ๋ฐฉ์ง€

// โš ๏ธ ์ฃผ์˜: update ๋‚ด๋ถ€์—์„œ ๋‹ค๋ฅธ StateFlow ์ฝ๊ธฐ ํ”ผํ•˜๊ธฐ
fun avoidDeadlock() {
    _stateA.update { stateA ->
        val stateB = _stateB.value
        stateA.copy(combined = stateA.data + stateB.data)
    }
}

๐Ÿ’ก ๊ณ ๊ธ‰ ํ™œ์šฉ ํŒจํ„ด

๐Ÿ”„ ์กฐ๊ฑด๋ถ€ ์—…๋ฐ์ดํŠธ ํŒจํ„ด

fun conditionalUpdate() {
    _counter.update { current ->
        when {
            current < 0 -> 0
            current > 100 -> 100
            else -> current + 1
        }
    }
}

๐ŸŽฏ ์ƒํƒœ ๋จธ์‹  ํŒจํ„ด

sealed class LoadingState {
    object Idle : LoadingState()
    object Loading : LoadingState()
    data class Success(val data: String) : LoadingState()
    data class Error(val message: String) : LoadingState()
}

class StateMachineViewModel : ViewModel() {
    private val _loadingState = MutableStateFlow<LoadingState>(LoadingState.Idle)
    
    fun startLoading() {
        _loadingState.update { state ->
            when (state) {
                is LoadingState.Idle -> LoadingState.Loading
                else -> state
            }
        }
    }
}

๐ŸŽ‰ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

MutableStateFlow์˜ value์™€ update ๋ฐฉ์‹์„ ์ ์ ˆํžˆ ํ™œ์šฉํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์ ์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐ŸŒŸ ์ฃผ์š” ์ด์ 

  • โœ… Thread-Safeํ•œ ์ƒํƒœ ๊ด€๋ฆฌ๋กœ ์•ˆ์ •์„ฑ ํ–ฅ์ƒ
  • โœ… Race Condition ๋ฐฉ์ง€๋กœ ์˜ˆ์ธก ๊ฐ€๋Šฅํ•œ ๋™์ž‘
  • โœ… CAS ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํ†ตํ•œ ์•ˆ์ „ํ•œ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ
  • โœ… ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ๋ฐ ์œ ์ง€๋ณด์ˆ˜์„ฑ ํ–ฅ์ƒ

๐Ÿ’ก ํ•ต์‹ฌ ๊ฐ€์ด๋“œ๋ผ์ธ

  1. ๋‹จ์ˆœ ๊ฐ’ ํ• ๋‹น โ†’ value ์‚ฌ์šฉ ๊ณ ๋ ค (๋‹จ์ผ ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ)
  2. ๋ณตํ•ฉ ๊ฐ์ฒด / ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ โ†’ update ํ•„์ˆ˜ ์‚ฌ์šฉ
  3. ์กฐ๊ฑด๋ถ€ ๋กœ์ง ํ•„์š” ์‹œ โ†’ update์˜ ํ•จ์ˆ˜ํ˜• ์ ‘๊ทผ ํ™œ์šฉ
  4. ์„ฑ๋Šฅ ์ตœ์ ํ™” โ†’ ์—ฌ๋Ÿฌ ์†์„ฑ์„ ํ•œ ๋ฒˆ์— ์—…๋ฐ์ดํŠธ
  5. ๋ฌด๊ฑฐ์šด ์—ฐ์‚ฐ โ†’ update ์™ธ๋ถ€์—์„œ ๋ฏธ๋ฆฌ ์ฒ˜๋ฆฌ

๐Ÿ”ง update ํ•จ์ˆ˜์˜ ํ•ต์‹ฌ ๋™์ž‘

while (true) {
    val prevValue = value                  // 1. ํ˜„์žฌ ๊ฐ’ ํš๋“
    val nextValue = function(prevValue)    // 2. ๋ณ€ํ™˜ ํ•จ์ˆ˜ ์‹คํ–‰
    if (compareAndSet(prevValue, nextValue)) { // 3. ์›์ž์  ๋น„๊ต ๋ฐ ๊ต์ฒด
        return  // ์„ฑ๊ณต ์‹œ ์ข…๋ฃŒ
    }
    // ์‹คํŒจ ์‹œ ์žฌ์‹œ๋„ (๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ’์„ ๋ณ€๊ฒฝํ–ˆ์Œ)
}

๐Ÿš€ ์ด์ œ MutableStateFlow๋ฅผ ๋” ์•ˆ์ „ํ•˜๊ณ  ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค!


๐Ÿ“Œ ๊ธฐ์–ตํ•˜์„ธ์š”

  • ์ƒํ™ฉ์— ๋งž๊ฒŒ value์™€ update๋ฅผ ์„ ํƒํ•˜์„ธ์š”.
  • ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œยท๋ณตํ•ฉ ๊ฐ์ฒดยท์กฐ๊ฑด๋ถ€ ๋กœ์ง์—๋Š” update๊ฐ€ ์•ˆ์ „ํ•ฉ๋‹ˆ๋‹ค.
  • ๋‹จ์ˆœ ๊ฐ’ ๋ณ€๊ฒฝ์ด๋‚˜ ์ดˆ๊ธฐํ™”์ฒ˜๋Ÿผ ๋ช…ํ™•ํ•œ ๊ฐ’ ์„ค์ •์—๋Š” value๊ฐ€ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ฌ ๋งˆ์ง€๋ง‰ ํ•œ ์ค„ ์š”์•ฝ

์˜ฌ๋ฐ”๋ฅธ ๋ฐฉ์‹์˜ ์ƒํƒœ ๋ณ€๊ฒฝ์€ ๋ฒ„๊ทธ๋ฅผ ์ค„์ด๊ณ , ์ฝ”๋“œ ํ’ˆ์งˆ์„ ๋†’์ด๋ฉฐ,
๋™์‹œ์„ฑ ๋ฌธ์ œ ์—†์ด ์•ˆ์ •์ ์ธ ์•ฑ ๋™์ž‘์„ ๋ณด์žฅํ•ฉ๋‹ˆ๋‹ค.

profile
Android Developer

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