기존에 callback 방식을 이용하여 비동기식으로 사용되고 있는 함수를 suspendCoroutine, suspendCancellableCoroutine, callbackFlow를 이용하여 동기식으로 이용하는 방법에 대해 알아보자. (callbackFlow는 추후 포스팅 예정)
callback함수를 통해 하나의 테이터만 전달할 경우 suspendCoroutine, suspendCancellableCoroutine 이용하고
지속적인 데이터 전달이 있을 경우는 callbackFlow를 사용하면 된다.
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
val safe = SafeContinuation(c.intercepted())
block(safe)
safe.getOrThrow()
}
}
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T =
suspendCoroutineUninterceptedOrReturn { uCont ->
val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
/*
* For non-atomic cancellation we setup parent-child relationship immediately
* in case when `block` blocks the current thread (e.g. Rx2 with trampoline scheduler), but
* properly supports cancellation.
*/
cancellable.initCancellability()
block(cancellable)
cancellable.getResult()
}
suspendCoroutine, suspendCancellableCoroutine은 위와 같이 suspend inline 함수로 되어있다.(inline함수는 new를 통해 새로운 객체를 생성하는 것을 방지해 준다.)
둘의 차이점은 suspendCancellableCoroutine 같은 경우는 코루틴이 취소 되었을 때 invokeOnCamcellation을 통해 추후 처리를 할 수 있다.
사용법
suspendCoroutine, suspendCancellableCoroutine 내에 비동기 작업을 수행하고 해당 비동기 작업 내에 결과 값을
resume해주면 된다.
코루틴 내에서 권한설정 여부를 동기식으로 사용하는 예제를 살펴보자
private var requestPermissionContinuation: CancellableContinuation<Boolean>? = null
@SuppressLint("MissingPermission")
private val requestFineLocationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
requestPermissionContinuation?.resume(isGranted) // 권한승인을 resume 해준다.
} else {
// requestPermissionContinuation?.resume(isGranted) 권한 거부를 resume해 줘도 되지만 resume 해줄 경우 setCamera()의 do something이 실행된다.
val builder: AlertDialog.Builder =
AlertDialog.Builder(this)
builder.setMessage("해당 앱을 이용하기 위해 설정 페이지에서 카메라 권한을 허용해 주세요.")
builder.setPositiveButton("OK") { dialog, id ->
val uri = Uri.fromParts("package",packageName, null)
val intent = Intent().apply {
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS
data = uri
}
startActivity(intent)
onBackPressed()
}
builder.setNegativeButton("CANCEL") { dialog, id ->
onBackPressed()
}
val alert: AlertDialog = builder.create()
alert.show()
}
}
private suspend fun requestPermission(permission: String) =
suspendCancellableCoroutine<Boolean> { continuation ->
requestPermissionContinuation = continuation
requestFineLocationPermissionLauncher.launch(permission)
continuation.invokeOnCancellation { //코루틴이 cancel 되었을 때 호출 된다.
requestPermissionContinuation = null
}
}
private suspend fun checkPermission(permission: String) {
if (ContextCompat.checkSelfPermission(
this,
permission
) != PackageManager.PERMISSION_GRANTED
) {
requestPermission(permission)
}
}
fun setCamera() {
lifecycleScope.launch {
checkPermission(Manifest.permission.CAMERA)
//do something
}
}
setCamera() 함수의 코루틴 내에서 checkPermission 함수를 호출하여 권한 체크를 하고 권한이 없을 경우 requestPermission 함수에서 권한 여부를 묻고 registerForActivityResult를 통해 결과를 받는 코드이다.
권한 여부의 결과에 따라 resume을 해주게 되면 setCamera() 함수의 do something 부분이 실행되게 된다. (resumeWith를 통해 result 타입으로 반환하거나 resumeWithException을 통해 예외 처리 또한 가능하다.)
따라서 코드를 작성할 때 resume 일어나 전 부분까지가 동기되는 작업이라는 것과 resume이 두번이상 호출되면 안되는 부분에 대해 유의 하여 코드를 작성 하여야 한다.
- 여기서 suspendCoroutine, suspendCancellableCoroutine과의 차이는 코루틴이 취소 되었을 경우 continuation.invokeOnCancellation부분이 호출 된다는 점이다.
주의점
- 만약 suspendCoroutine, suspendCancellableCoroutine을 사용할 때 여러번의 resume이 호출 된다면 Fatal Exception: java.lang.IllegalStateException Already resumed 와 같은 exception이 발생한다.
- 코루틴 내에서 suspend로 실행되기 때문에 resume이 될 때까지 기다림으로 설계를 제대로 해야 된다.
'코틀린' 카테고리의 다른 글
코틀린 by란? (0) | 2022.06.28 |
---|---|
코틀린 플로우 (0) | 2022.06.01 |
코틀린 StateFlow 및 SharedFlow (2) | 2022.05.21 |
코루틴 채널 개념 및 예제 (0) | 2022.05.07 |
코틀린 코루틴 개념 및 기본 사용 용도 정의 (0) | 2021.06.23 |