Kotlin Coroutines๋ Android ํ๊ฒฝ์์ ๋น๋๊ธฐ(Asynchronous) ํ๋ก๊ทธ๋๋ฐ์ ์ํ ๊ฐ์ฅ ํ๋์ ์ด๊ณ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ๋๋ค. ๋ฉ์ธ ์ค๋ ๋(UI ์ค๋ ๋)๋ฅผ ๋ธ๋กํนํ์ง ์๊ณ , ๋คํธ์ํฌ ์์ฒญ์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ๊ณผ ๊ฐ์ ์ค๋ ๊ฑธ๋ฆฌ๋ ์์ ์ ์ฒ๋ฆฌํ ์ ์๊ฒ ํด์ค๋๋ค.
ํต์ฌ์ suspend ํจ์์ **๊ตฌ์กฐํ๋ ๋์์ฑ(Structured Concurrency)**์
๋๋ค.
Kotlin Coroutines๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด build.gradle.kts ํ์ผ์ ์์กด์ฑ์ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
// build.gradle.kts (Module: app)
dependencies {
// ์ฝ๋ฃจํด ํต์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
// Android ํ๊ฒฝ์์ ์ฌ์ฉ ์
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
// ViewModel์์ ์ฝ๋ฃจํด ์ฌ์ฉ ์
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
}
์๋๋ก์ด๋ ์ฑ์์ ViewModel ๋ด์์ ๋คํธ์ํฌ ์์ ์ ์ํํ๊ณ ๊ฒฐ๊ณผ๋ฅผ UI์ ๋ฐ์ํ๋ ํ์ค์ ์ธ ์ฝ๋ฃจํด ํ์ฉ ์์์ ๋๋ค.
suspend ํจ์ ์ ์ (๋ฐ์ดํฐ ๋ ์ด์ด)๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ์ด๋ Retrofit์ ์ฌ์ฉํ ๋คํธ์ํฌ ์์ฒญ ๋ฑ ์๊ฐ์ด ์ค๋ ๊ฑธ๋ฆฌ๋ ์์
์ suspend ํค์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ํฉ๋๋ค. ์ด ํจ์๋ ์ฝ๋ฃจํด ์ค์ฝํ ๋ด์์๋ง ํธ์ถ๋ ์ ์์ผ๋ฉฐ, ์คํ ์ค ๋ธ๋กํน ์์ด ์ ์ ๋ฉ์ท๋ค๊ฐ(suspending) ๋์ค์ ์ฌ๊ฐ๋ฉ๋๋ค.
// 1. Data Class (์์)
data class User(val id: Int, val name: String)
// 2. Repository (๋ฐ์ดํฐ ๋ ์ด์ด)
class UserRepository {
// suspend ํค์๋: ์ด ํจ์๊ฐ ๋น๋๊ธฐ ์์
์ ์ํํจ์ ๋ช
์
suspend fun fetchUser(userId: Int): User {
// withContext๋ฅผ ์ฌ์ฉํ์ฌ IO ์ค๋ ๋๋ก ์ ํ (๋คํธ์ํฌ, DB ์์
์ ์ ํฉ)
return withContext(Dispatchers.IO) {
// ์ค์ ๋คํธ์ํฌ ์์ฒญ์ด๋ DB ์ฟผ๋ฆฌ ์ฝ๋๊ฐ ์ฌ๊ธฐ์ ๋ค์ด๊ฐ๋๋ค.
// ์๋ฎฌ๋ ์ด์
: 2์ด ๋์ ์ง์ฐ (๋ธ๋กํน ์์ด)
kotlinx.coroutines.delay(2000)
// ๊ฒฐ๊ณผ ๋ฐํ
User(userId, "์ฌ์ฉ์ #$userId")
}
}
}
ViewModel์์๋ viewModelScope๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๋ฃจํด์ ์์ํฉ๋๋ค. viewModelScope๋ ViewModel์ ์๋ช
์ฃผ๊ธฐ์ ํจ๊ป ์์ํ๊ณ , ViewModel์ด ํ๊ดด๋ ๋ ์ฝ๋ฃจํด์ ์๋์ผ๋ก ์ทจ์(Cancel)ํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ๋ฐฉ์งํฉ๋๋ค (๊ตฌ์กฐํ๋ ๋์์ฑ).
// 1. ViewModel (๋น์ฆ๋์ค ๋ก์ง ๋ ์ด์ด)
class UserViewModel(private val repository: UserRepository) : ViewModel() {
// UI์ ๋ณด์ฌ์ค ์ํ (LiveData ๋๋ StateFlow ์ฌ์ฉ)
private val _userState = MutableStateFlow<User?>(null)
val userState: StateFlow<User?> = _userState
fun loadUserData(userId: Int) {
// 2. viewModelScope๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๋ฃจํด ์์
// ViewModel์ด ์ด์์๋ ๋์ ์ฝ๋ฃจํด์ด ์คํ๋ฉ๋๋ค.
viewModelScope.launch {
try {
// 3. suspend ํจ์ ํธ์ถ (awaiting its result)
val user = repository.fetchUser(userId)
// 4. ๊ฒฐ๊ณผ ์ฒ๋ฆฌ: UI ์ค๋ ๋์์ ์๋์ผ๋ก ์คํ๋จ
_userState.value = user
} catch (e: Exception) {
// ์๋ฌ ์ฒ๋ฆฌ
Log.e("UserViewModel", "๋ฐ์ดํฐ ๋ก๋ ์ค ์ค๋ฅ ๋ฐ์", e)
}
}
}
}
๋ ๊ฐ์ง ์ด์์ ๋
๋ฆฝ์ ์ธ ๋น๋๊ธฐ ์์
์ ๋์์ ์คํํ๊ณ ๋ชจ๋ ๊ฒฐ๊ณผ๊ฐ ๋์ฐฉํ ๋๊น์ง ๊ธฐ๋ค๋ ค์ผ ํ ๊ฒฝ์ฐ, async ๋น๋์ await ํจ์๋ฅผ ์ฌ์ฉํฉ๋๋ค.
class DataService(private val repository: UserRepository) {
// ๋ ์์
์ ๋์์ ์คํ
suspend fun loadDashboardData(userId: Int): Pair<User, String> {
// CoroutineScope ๋ด์์ async๋ฅผ ์ฌ์ฉํ์ฌ ์์
์ ๋ณ๋ ฌ๋ก ์์
val userDeferred = CoroutineScope(Dispatchers.IO).async {
repository.fetchUser(userId)
}
val settingDeferred = CoroutineScope(Dispatchers.IO).async {
fetchUserSettings() // ๋ณ๋์ suspend ํจ์ ๊ฐ์
}
// await()๋ฅผ ์ฌ์ฉํ์ฌ ๋ ์์
์ด ๋ชจ๋ ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค.
val user = userDeferred.await()
val settings = settingDeferred.await()
return Pair(user, settings)
}
// ์ค์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๋ ๋ ๋ค๋ฅธ suspend ํจ์ (์์)
private suspend fun fetchUserSettings(): String {
delay(1500) // 1.5์ด ์ง์ฐ
return "Loaded Settings"
}
}
| ๊ฐ๋ | ์ค๋ช | ํ์ฉ |
|---|---|---|
suspend | ํจ์๊ฐ ์คํ ์ค ์ ์ ๋ฉ์ถ ์ ์๋(Non-blocking) ๋น๋๊ธฐ ํจ์์์ ํ์. | ๋ชจ๋ ๋คํธ์ํฌ/DB ์ ๊ทผ ํจ์ ์ ์. |
launch | ์ฝ๋ฃจํด์ ์์ํ๊ณ ๊ฒฐ๊ณผ๋ฅผ ๋ฌด์ํ๊ฑฐ๋(fire-and-forget), ์ํ๋ฅผ ์ ๋ฐ์ดํธํ ๋ ์ฌ์ฉ. | ViewModel์์ UI ์ ๋ฐ์ดํธ ์ฝ๋ฃจํด ์์. |
async / await | ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ ์ฝ๋ฃจํด์ ์์(async)ํ๊ณ , ๋์ค์ ๊ทธ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ์ ๋(await) ์ฌ์ฉ. ๋ณ๋ ฌ ์ฒ๋ฆฌ์ ์ฃผ๋ก ํ์ฉ๋จ. | ์ฌ๋ฌ API ํธ์ถ์ ๋์์ ์ํ. |
Dispatchers | ์ฝ๋ฃจํด์ด ์คํ๋ ์ค๋ ๋(์ปจํ ์คํธ)๋ฅผ ์ง์ . | Dispatchers.Main (UI), Dispatchers.IO (๋คํธ์ํฌ/DB), Dispatchers.Default (CPU ์ง์ฝ ์์
). |
withContext | ํ์ฌ ์ฝ๋ฃจํด์ด ์คํ๋๋ ์ปจํ ์คํธ๋ฅผ ๋ณ๊ฒฝ(์ค๋ ๋ ์ ํ)ํ๊ณ ์์ ์ ์ํ ํ ์๋ ์ปจํ ์คํธ๋ก ๋ณต๊ท. | Dispatchers.Main์์ Dispatchers.IO๋ก ์ ํํ์ฌ ๋คํธ์ํฌ ์์ฒญ ์ํ. |