์ง์ญํ์๋ฉด Continuation Passing Style == ์ฐ์ ์ ๋ฌ ์คํ์ผ์ด๋ผ๊ณ ํ ์ ์๋ค. suspend๋ฅผ ๊ตฌํํ ๋ ํ์๋ก ํ์ํ ๊ฒ์ด suspend ๋ด๋ถ์์ cps๊ฐ ์ด๋ป๊ฒ suspend๋ฅผ ์คํ์ํค๋๊ฐ์ธ๋ฐ ์ด๊ฒ์ ์ด๋ป๊ฒ ๊ตฌํํ ์ ์์๊น.. ๋ฐ๋ก cps์ด๋ค. suspend๊ฐ ์ด๋ป๊ฒ ์๋๋๋์ง cps๋ฅผ ํตํด ์์๋ณด์.
์ผ๋จ Coroutine์ ๊ธฐ์กด์ ์ฝ๋ฐฑ ๊ธฐ๋ฐ ๋น๋๊ธฐ ์ฝ๋๋ฅผ ์์ฐจ์ (Sequential)์ธ ์ฝ๋๋ก ๋ฐ๊ฟ์ฃผ๊ธฐ ๋๋ฌธ์ ๋น๋๊ธฐ ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ ํ ์ ์๋ค.
์ด ์ ๋๋ฌธ์ ์ผ๋ช ์ฝ๋ฐฑ ์ง์ฅ์์ ๋ฒ์ด๋๊ฒ ํด์ฃผ๋ฉด์ ๊ฐ๋ ์ฑ์ ์ฌ๋ ค์ฃผ์ด ๋น๋๊ธฐ์ ์ธ ์ฝ๋๋ฅผ ๊ฐ๊ฒฐํ, ๊ฐ๋ ์ฑ์๊ฒ ๋ฐ๊ฟ์ค๋ค.
๊ทธ๋ผ Coroutine์ด ์ด๋ฌํ ์ผ๋ช , ๋ง์๋ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ ์ด์ ๊ฐ ๋ฌด์์ผ๊น? ๋ฐ๋ก๋ฐ๋ก suspend ํค์๋๋ฅผ ๋ณด๋ฉด ์ ์ ์๋๋ฐ suspend๋ ๋ด๋ถ์ ์ผ๋ก ์ฝ๋ฐฑ์ ์์ฑํ๊ณ suspned ํค์๋๋ฅผ ์ฝ์ด๋ค์ธ kotlin์ ์ปดํ์ผ๋ฌ๋ suspend์ resume์ ์ํ ์ฝ๋ฐฑ ์ฝ๋๋ฅผ ๋ง๋ค์ด์ค๋ค. ํ ๋ฒ ๊ทธ ์ ์์ ๋ณด๋ฌ ๊ฐ๋ณด์.
continuation๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด๋ค. coroutine์๋ CPS ํจ๋ฌ๋ค์์ด ์ ์ฉ๋์ด์๋ค๊ณ ํ๋ค. CPS๋ ํธ์ถ๋๋ ํจ์์ Continuation์ ์ ๋ฌํ๊ณ , ๊ฐ ํจ์์ ์์ ์ด ์๋ฃ๋๋๋๋ก ์ ๋ฌ๋ฐ์ Continuation์ ํธ์ถํ๋ ํจ๋ฌ๋ค์์ ์๋ฏธํ๋ค. ์ด๋ฐ ๋ถ๋ถ์์ Continuation์ ์ผ์ข ์ ์ฝ๋ฐฑ์ด๋ผ๊ณ ์๊ฐํ ์ ์๋ค. ๊ทธ๋ผ ์ด Continuation์ ์ด๋ป๊ฒ ์ ๋ฌํ๊ณ , ๊ทธ ์ด์ ์ ๋ํด ์์๋ณด์.
์ฐ์ Continuation ์ฝ๋๋ฅผ ๋ณด์.
/**
* Interface representing a continuation after a suspension point that returns a value of type `T`.
*/
@SinceKotlin("1.3")
public interface Continuation<in T> {
/**
* The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}
์ถ์ฒ : Corotuine ๊ณต์๋ฌธ์ ์ค -
์ฝ๋ฃจํด์ ๋ค์์ ๋ฌด์จ ์ผ์ ํด์ผ ํ ์ง ๋ด๊ณ ์๋ ํ์ฅ๋ ์ฝ๋ฐฑ์ด๋ค. ์ ์ฝ๋๋ฅผ ํ์ด์ ๋ณด์๋ฉด Continuation ์ธํฐํ์ด์ค์๋ ํฌ๊ฒ context ๊ฐ์ฒด์ resumeWith๋ผ๋ ํจ์๊ฐ ์๋ค.
- resumeWith ํจ์๋ ํน์ ํ ํจ์๊ฐ suspend ๋์ด์ผ ๋ ๋ ํ์ฌ ํจ์์์ ํน์ ํ ํจ์์ ๊ฒฐ๊ณผ ๊ฐ์ T๋ก ๋ฐ๊ฒ ํด์ฃผ๋ ํจ์์ด๋ค.
- context๋ ๊ฐ Continuation์ด ํน์ ์ค๋ ๋๋ ์ค๋ ๋ ํ์์ ์คํ๋๋ ๊ฒ์ ํ์ฉํด์ค๋ค.
CPS์์๋ Continuation์ ์ ๋ฌํด์ฃผ๋ฉด์ ๋น์ฅ suspend๋ ๋ถ๋ถ์์ resume์ ๊ฐ๋ฅํ๊ฒ ํด์ค๋ค. Continuation์ resume๋์์ ๋์ ๋์ ๊ด๋ฆฌ๋ฅผ ์ํ ๊ฐ์ฒด๋ก, ์ฐ์์ ์ธ ์ํ๊ฐ์ ์์ฌ์ํตํ๋ ์ญํ ์ ๋งก๊ณ ์๋ค๊ณ ํ ์ ์๋ค.
์์ฝํ๋ฉด Continuation์ ํธ์ถ ํจ์ ๊ฐ์ ์ผ์ ์ค๋จ, ์ฌ๊ฐ๋ฅผ ์ํ ์์ฌ์ํตํ๋ ์ญํ ์ด๊ณ , CPS๋ ํจ์ ํธ์ถ ์์ ์ด Continuation์ ์ ๋ฌํ๋ ํจ๋ฌ๋ค์์ด๋ค. Coroutine ์ญ์ CPS๋ก ๊ตฌํ๋์๋ค.
Continuation์ kotlin ์ปดํ์ผ๋ฌ๊ฐ suspend ํจ์์ ์๊ทธ๋์ฒ๋ฅผ ๋ณ๊ฒฝํ๋ฉด์ ์ฌ์ฉ๋๋ค.
-
Continuation ๊ฐ์ฒด๊ฐ ๊ธฐ์กด suspend ํจ์์ ํ๋ผ๋ฏธํฐ์ ์ถ๊ฐ๋๊ณ suspend ํค์๋๊ฐ ์ฌ๋ผ์ง๋ค.
-
์ด ๊ฐ์ฒด๋ suspend ํจ์ ๊ณ์ฐ์ ๊ฒฐ๊ณผ๋ฅผ ํธ์ถํ Coroutine์ ์ ๋ฌํ๋๋ฐ ์ฌ์ฉ๋๋ค.
-
๋ํ ํจ์์ ๋ฆฌํด ํ์ ๋ํ androidApp์์ Unit์ผ๋ก ๋ณ๊ฒฝ๋์๋๋ฐ, ๊ฒฐ๊ณผ๋ resume ํจ์์ ๋งค๊ฐ๋ณ์๋ฅผ ํตํด ์ป์ ์ ์๊ฒ ๋๋ค.
Letโs Code!
suspend fun getAndroidApp(api: AndroidApi): AndroidApplication {
val sellerApp = api.fetchSeller()
val guestApp = api.fetchGuest(sellerApp)
val masterWeb = api.fetchMaster(guestWeb)
val androidApplication = api.fetchAndroidApp(masterWeb)
Log.d("๋๋์ด ์๋๋ก์ด๋ ์ฑ์ ์์ ๋ฃ์๋ค!")
return androidApplication
}
์ด๋ฌ๋ ์ฝ๋๊ฐ
fun getAndroidApp(api: AndroidApi, completion: Continuation<Any?>) {
val sellerApp = api.fetchSeller()
val guestApp = api.fetchGuest(sellerApp)
val masterWeb = api.fetchMaster(guestWeb)
val androidApplication = api.fetchAndroidApp(masterWeb)
Log.d("๋๋์ด ์๋๋ก์ด๋ ์ฑ์ ์์ ๋ฃ์๋ค!")
completion.resume(๏ฟฝAndroidApplication)
}
์๋ ๊ฒ ๋ณํ์ด์!
๊ทธ๋ผ suspend, resume ์ฐ์ฐ์ด ์ด๋ป๊ฒ ๊ฐ๋ฅํ ๊ฑธ๊น?
์ด๋ด ๋โฆ Letโs Code!
fun getAndroidApp(api: AndroidApi, completion: Continuation<Any?>) {
val sellerApp = api.fetchSeller()
val guestApp = api.fetchGuest(sellerApp)
val masterWeb = api.fetchMaster(guestApp)
val androidApplication = api.fetchAndroidApp(masterWeb)
Log.d("๋๋์ด ์๋๋ก์ด๋ ์ฑ์ ์์ ๋ฃ์๋ค!")
completion.resume(androidApllication)
}
fun getAndroidApp(api: AndroidApi, completion: Continuation<Any?>) {
when(label) {
0 -> // Label 0
val sellerApp = api.fetchSeller()// suspend
1 -> // Label 1
val guestApp = api.fetchGuest(sellerApp)// suspend
2 -> // Label 2
val masterWeb = api.fetchMaster(guestApp)// suspend
3 -> // Label 3
val androidApplication = api.fetchAndroidApp(masterWeb)// suspend
4 -> { // Label 4
Log.d("๋๋์ด ์๋๋ก์ด๋ ์ฑ์ ์์ ๋ฃ์๋ค!")
completion.resume(AndroidApllication)
}
}
}
kotlin ์ปดํ์ผ๋ฌ๋ ๋ชจ๋ ์ค๋จ ๊ฐ๋ฅํ ์ง์ ์ ์ฐพ์ when์ผ๋ก ํํํ๋ค. Kotlin ์ปดํ์ผ๋ฌ๋ ํจ์๊ฐ ๋ด๋ถ์ ์ผ๋ก ์ค๋จ ๊ฐ๋ฅ ์ง์ ์ ์๋ณํ๊ณ , ์ด ์ง์ ๋ค์๊ฒ label์ ๋ถ์ฌํ๊ณ ์ฝ๋๋ฅผ ๋ถ๋ฆฌํด์ ๊ฐ๊ฐ์ ์ฝ๋๋ค์ label๋ก ์ธ์ํ๋ค. ์ด๋ ๊ฒ ์ฝ๋๋ฅผ ๋ช ํํ ๊ตฌ๋ถํด๋๊ณ , ์ค๋จ ๊ฐ๋ฅ ์ง์ ์์ suspend๋๊ณ ํด๋น ์์ ์ด ๋๋ resume๋๋ฉด ๋ค์ ๋ค์ ์ฝ๋ ๊ตฌ๋ฌธ์ ์คํํ ์ ์๊ฒ๋๋ค. ์ด๋ฐ ๋ฐฉ์์ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ ํ๋์ ๋ฐฉ๋ฒ์ด๋ผ ํด์ state machine(์ํ ๋จธ์ )์ด๋ผ๊ณ ๋ถ๋ฆฐ๋ค.
์ฆ, Kotlin ์ปดํ์ผ๋ฌ๋ suspend ํจ์๋ฅผ ์ปดํ์ผ ํ ๋ ์ํ๋จธ์ ์ผ๋ก ํํํ๋ค. kotlin ์ปดํ์ผ๋ฌ๋ suspend ํจ์๋ฅผ ํธ์ถํ ๋๋ง๋ค ๊ตฌ์ญ์ ๋๋๊ณ , ๋ผ๋ฒจ๋งํด์ when๋ฌธ์ผ๋ก ํํํ๋ค. ์ฌ๊ธฐ์ ๋ผ๋ฒจ(label)๊ณผ, suspend ํจ์ ๋ด๋ถ์์ ์ ์ธ๋ ๋ณ์๋ค์ ์ด๋ป๊ฒ ๊ด๋ฆฌํ ๊น?
kotlin ์ปดํ์ผ๋ฌ๋ ๊ธฐ์กด์ ๋ง๋ค์๋ ํจ์ ์์ ํด๋์ค๋ฅผ ์์ฑํ๋ค. ์ด ํด๋์ค๋ Continuation์ ์์๊ฐ๋ ์ธ CoroutineImpl์ ๊ตฌํํ๋ฉฐ, ๊ธฐ์กด์ suspend ํจ์์ ์ ์ธ๋ ๋ณ์๋ค๊ณผ, ์คํ ๊ฒฐ๊ณผ์ธ Result, ํ์ฌ์ ์งํ ์ํ์ธ label์ ๊ฐ์ง๊ณ ์๋ค.
fun getAndroidApp(api: AndroidApi, completion: Continuation<Any?>) {
class GetAndroidAppStateMachine(completion: Continuation<Any?>): CoroutineImpl(completion) {
//๊ธฐ์กด์ ํจ์ ๋ด๋ถ์ ์ ์ธ๋ ๋ณ์๋ค๋ค๋ค
var sellerApp : SellerApp? = null
var guestApp : GuestApp? = null
var masterWeb = MasterWeb? = null
var androidApplication : AndroidApplication? = null
var result: Any? = null
var label: Int = 0
override fun invokeSuspend(result: Any?) {
this.result = result
getAndroidApp(api, this)
}
}
}
๊ทธ๋ผ ์ด ํด๋์ค๊ฐ ์ด๋ป๊ฒ ์ฌ์ฉ๋๋์ง ์์๋ณด๋ฌ Letโs Code!
fun getAndroid(api: AndroidApi, completion: Continuation<Any?>) {
val continuation = completion as? GetAndroidAppStateMachine ?: GetAndroidAppStateMachine
(completion)
when (continuation.label) {
0 -> {
throwOnFailure(continuation.result)
continuation.label = 1
api.fetchSeller(continuation) //suspend
}
1 -> {
throwOnFailue(continuation.result)
continuation.sellerApp = continuation.result as SellerApp
continuation.label = 2
api.fetchGuest(continuation.sellerApp, continuation) //suspend
}
2 -> {
throwOnFailue(continuation.result)
continuation.guestApp = continuation.result as GuestApp
continuation.label = 3
api.fetchMaster(continuation.guestApp, continuation) //suspend
}
3 -> {
throwOnFailue(continuation.result)
continuation.masterWeb = continuation.result as
continuation.label = 4
api.fetchAndroidApp(continuation.masterWeb, continuation) //suspend
}
4 -> {
throwOnFailue(continuation.result)
continuation.androidApplication = continuation.result as AndroidApplication
Log.d("๋๋์ด ์๋๋ก์ด๋ ์ฑ์ ์์ ๋ฃ์๋ค!")
completion.resume(continuation.androidApllication)
}
else -> throw IllegalStateException
}
}
์๊น 3๋ฒ์งธ ๋จ๊ณ์์ ์ผ์ ์ค๋จ ๊ฐ๋ฅํ ์ง์ ์ ๋๋ ์ด์ ๊ฐ ์ด ๊ฒ ๋๋ฌธ์ธ๋ฐ, Kotlin ์ปดํ์ผ๋ฌ๊ฐ ์์ฑํด์ค GetAndroidAppStateMachine์ label์ด when ์์์ ์ฌ์ฉ๋๋๋ฐ, ์ผ์ ์ค๋จํ ์ง์ ์ ๋ฐ๋ผ ์ฝ๋๋ฅผ ๋๋๊ณ label๋ก ๊ทธ ์ฝ๋๋ฅผ ๊ตฌ๋ถํ์ผ๋, ์ด์ resumeํ๊ธฐ ์ํด label์ ์ฌ์ฉํ ์ฐจ๋ก์ด๋ค. ๊ฐ label๋ถ๊ธฐ์์ label์ ๊ฐ์ ์ฆ๋ถ ์์ผ์ ๋ค์ ์คํํ ์ง์ ์ ๊ฐ๋ฆฌํค๊ณ , ์ด๋ฌํ ์ ์ฐจ๋ฅผ ๊ฑฐ์ณ resume์ด ๋๋ค.
ํํธ GetAndroidAppStateMachine์ ์ดํด๋ณด๋ฉด ๊ธฐ์กด์ suspend ํจ์ ๋ด๋ถ์ ์ ์ธ๋ ๋ณ์๋ค์ด null๋ก ์ด๊ธฐํ๋ ๋ชจ์ต์ ๋ณผ ์๊ฐ ์๋๋ฐ, ์ด ๋ํ when ์ ์์์ resume์ suspend๋๊ณ ์คํ๋ ์์ ์ ๊ฒฐ๊ณผ๊ฐ continuation.result๋ก ๋ฐ์์ค๋ ๋ชจ์ต๋ ํ์ธํ ์๊ฐ ์๋ค. continuation.result๋ฅผ ๊ฐ ํ์ ์ผ๋ก ์บ์คํ ํด์ ํจ์ ๋ด๋ถ์์ ์ ์ธ๋ ๋ณ์๋ค์ ๊ด๋ฆฌํ๊ณ ์๋ค. ์ด๋ ๊ฒ ๊ฐ ์ฝ๋ ๋ธ๋ก์ ํ๋ํ๋ ์คํํ ๋๋ง๋ค ํจ์์ ์ํ๊ฐ ์ ์ ๋์ ๋๊ณ ๋ง์ง๋ง resume์ ํตํด ์ต์ข ๊ฐ์ด ์ ๋ฌ๋๊ฒ ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ํ๋์ Coroutine ์ฝ๋๊ฐ ๋๋๊ฒ ๋๋ค.
์ด ๊ธ๋ก์จ Kotlin ์ปดํ์ผ๋ฌ๊ฐ suspend ํค์๋๋ฅผ ๋์ํ๋ ๊ณผ์ ์ ๋ํด ์์๋ณด์๋ค. ๋จธ๋ฆฌ๊ฐ ์ํ๊ณ ์๋ฒฝํ ์ดํดํ์ง ๋ชปํ์ง๋ง ์ด๋ฐ ํ๋ฆ์ด๊ตฌ๋.. ํ๊ณ ์ดํดํ๊ณ ๋ค์์ ๋ ์์ธํ ์์๋ด์ผ๊ฒ ๋คโฆ..suspend๊ฐ ์ด๋ ๊ฒ ๋ณต์กํ ๊ณผ์ ์ ๊ฑฐ์น๋คโฆ. ์ด์ ๋ถํฐ suspned ์ธ ๋๋ง๋ค JetBrain๋ณธ์ฌ๋ฅผ ํฅํด ์ ํ ๋ฒ์ฉ ํ๊ณ ์ฐ๊ธธ ๋ฐ๋๋คโฆ
๐คธโโ๏ธ โ ๊ทธ๋์ ํ๋ ์ค..