코틀린

Kotlin 코루틴(Coroutine)

태인킴 2020. 12. 7. 23:31
반응형


1. Kotlin 코루틴(Coroutine)

코루틴(Coroutine)비동기적으로 실행되는 코드를 간소화 하기 위해 만들어진 API 입니다. 코루틴은 Kotlin 버전 1.3에 추가되었으며 다른 언어에서 확립된 개념을 기반으로 합니다. 코루틴(Coroutine)을 사용하는 전문 개발자 중 50% 이상이 생산성이 향상되었다고 보고했습니다.

 

 

2. 특징

- 경량 : 코루틴을 실행 중인 스레드를 차단하지 않는 Suspending을 지원하므로 단일 스레드에서 많은 코루틴을 실행할 수 있습니다. Suspending 많은 동시 작업을 지원하면서도 차단 보다 메모리를 절약 합니다.

- 메모리 누수 감소 : scope과 같은 개념(구조화된 동시 실행)을 사용하여 범위 내에서 작업을 실행 하여 메모리 누수를 방지 합니다.

- 기본으로 제공되는 취소 지원 : 실행 중인 코루틴 계층 구조를 통해 자동으로 취소가 전달 됩니다.

- Jetpack 통합 : Android KTX와 같은 Jetpack 라이브러리코루틴을 지원 합니다.

 

 

3. 종속 항목 정보

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}

그래들의 다음과 같은 코루틴 버전을 추가 합니다.

 

 

4. 예제 - 네트워크 호출

Repository - ViewModel 사이에서 네트워크 요청을 보내고 결과를 UI 스레드로 반환 하고, 결과를 사용자에게 보여주는 예제를 만들어 보겠습니다. 네트워크 요청은 UI 스레드에서 하지 않고, IO 스레드에서 동작해야 합니다. 먼저, Repository 클래스를 작성하여, 네트워크 요청 코드를 구현 하겠습니다.

sealed class Result<out R> {
    data class Success<out T>(val data: T) : Result<T>()
    data class Error(val exception: Exception) : Result<Nothing>()
}

class LoginRepository(private val responseParser: LoginResponseParser) {
    private const val loginUrl = "https://example.com/login"

    // Function that makes the network request, blocking the current thread
    fun makeLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {
        val url = URL(loginUrl)
        (url.openConnection() as? HttpURLConnection)?.run {
            requestMethod = "POST"
            setRequestProperty("Content-Type", "application/json; utf-8")
            setRequestProperty("Accept", "application/json")
            doOutput = true
            outputStream.write(jsonBody.toByteArray())
            return Result.Success(responseParser.parse(inputStream))
        }
        return Result.Error(Exception("Cannot open HttpURLConnection"))
    }
}

위 코드에서 makeLoginRequest() 함수는 특별한 비동기 코드가 없으므로, 동기식으로 동작하며, 네트워크 응답이 올때 까지 호출한 스레드는 block(차단) 됩니다.

 

 

class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun login(username: String, token: String) {
        val jsonBody = "{ username: \"$username\", token: \"$token\"}"
        loginRepository.makeLoginRequest(jsonBody)
    }
}

위의 코드에서는 LoginViewModel은 특별한 Worker Thread를 사용하지 않으므로 UI Thread를 통해 makLoginRequest() 함수를 호출하여, UI Thread가 block(차단) 됩니다. 이 로직새로운 코루틴을 만들고 I/O Thread에서 makeLoginRequest()를 실행 하도록 만들겠습니다.

 

 

dependencies {
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
}

먼저, lifecycle-viewmodel-ktx 라이브러리 종속성을 추가해 줍니다. 밑에서 viewModelScope API를 사용하기 위해서 필요 합니다.

 

 

class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun login(username: String, token: String) {
        // Create a new coroutine to move the execution off the UI thread
        viewModelScope.launch(Dispatchers.IO) {
            val jsonBody = "{ username: \"$username\", token: \"$token\"}"
            loginRepository.makeLoginRequest(jsonBody)
        }
    }
}

viewModelScope는 ViewModel KTX 확장 프로그램에 포함된 사전 정의된 CoroutineScope입니다. 모든 코루틴CoroutineScope 내에서 실행해야 합니다. CoroutineScope는 하나 이상의 관련 코루틴을 관리합니다. launch코루틴을 만들고 함수 본문의 실행을 해당하는 Dispatchers.IO 스레드에 전달하는 함수입니다. 이 코루틴은 viewModelScope로 시작되므로 ViewModel 범위에서 실행됩니다. ViewModel이 소멸되는 경우 viewModelScope가 자동으로 취소되고 실행 중인 모든 코루틴도 취소됩니다.

 

 

class LoginRepository(...) {
    ...
    suspend fun makeLoginRequest(
        jsonBody: String
    ): Result<LoginResponse> {

        // Move the execution of the coroutine to the I/O dispatcher
        return withContext(Dispatchers.IO) {
            // Blocking network request code
        }
    }
}

makeLoginRequest() 함수 자체를 Dispatchers.IO 스레드에서만 동작 하도록 만들어, 스레드를 강제 합니다. 코루틴 라이브러리의 withContext() 함수를 사용하여 코루틴 실행을 다른 스레드로 이동합니다. makeLoginRequest() 함수에는 suspend 키워드도 표시됩니다. 이 키워드는 코루틴 내에서 만, 함수가 호출되도록 강제하는 Kotlin의 키워드 입니다.

 

 

class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun login(username: String, token: String) {

        // Create a new coroutine on the UI thread
        viewModelScope.launch {
            val jsonBody = "{ username: \"$username\", token: \"$token\"}"

            // Make the network call and suspend execution until it finishes
            val result = loginRepository.makeLoginRequest(jsonBody)

            // Display result of the network request to the user
            when (result) {
                is Result.Success<LoginResponse> -> // Happy path
                else -> // Show error in UI
            }
        }
    }
}

makeLoginRequest가 suspend 함수이므로 viewModelScope 코루틴에서 실행되어야 합니다. viewModelScope.launch()의 아무런 파라미터도 주입되지 않으므로, 기본 UI Thread에서 실행 됩니다. makeLoginRequest() 함수의 withContext 블록이 완료되면 login()의 코루틴이 네트워크 요청의 결과와 함께 UI 스레드에서 실행을 재개합니다.

 

 

예외 처리

class LoginViewModel(
    private val loginRepository: LoginRepository
): ViewModel() {

    fun makeLoginRequest(username: String, token: String) {
        viewModelScope.launch {
            val jsonBody = "{ username: \"$username\", token: \"$token\"}"
            val result = try {
                loginRepository.makeLoginRequest(jsonBody)
            } catch(e: Exception) {
                Result.Error(Exception("Network request failed"))
            }
            when (result) {
                is Result.Success<LoginResponse> -> // Happy path
                else -> // Show error in UI
            }
        }
    }
}

위와 같이, kotlin의 기본 try / catch 문으로 makeLoginRequest() 함수의 예외 처리를 할수 있습니다.

반응형