10
13

 

์ฝ”๋ฃจํ‹ด์ด๋ž€?

์ฝ”๋ฃจํ‹ด์€ ์ง€์—ฐ๊ฐ€๋Šฅํ•œ ์ธ์Šคํ„ด์Šค์ด๊ณ  ๋น„์„ ์ ํ˜•(OS๊ฐ€ ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์—๊ฒŒ๋กœ ๊ฐ•์ œ์ „ํ™˜ํ•˜๋Š”) ๋ฉ€ํ‹ฐํƒœ์Šคํ‚น์„ ์ˆ˜ํ–‰ํ•˜๋Š” ์„œ๋ธŒ๋ฃจํ‹ด(==ํ•จ์ˆ˜)์„ ์ผ๋ฐ˜ํ™”ํ•œ ๊ฒƒ์ด๋‹ค. "์ง€์—ฐ๋œ๋‹ค"๋Š” ์˜๋ฏธ๋Š” ํŠน์ •์‹œ์ ์— ๋ฉˆ์ถฐ๋†“๊ณ  ์—ฐ์‚ฐํ•œ๋‹ค์Œ, ํ•„์š”์— ๋”ฐ๋ผ ์‚ฌ์šฉํ•  ์ง€ ๋ง ์ง€ ๊ฒฐ์ •ํ•œ๋‹ค๊ณ  ์ดํ•ดํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค. ์ฝ”๋ฃจํ‹ด์€ ์“ฐ๋ ˆ๋“œ์— ๋น„ํ•ด ์ ์€ ๋ฆฌ์†Œ์Šค๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.

์„œ๋ธŒ๋ฃจํ‹ด:
์—ฌ๋Ÿฌ ๋ช…๋ น์–ด๋ฅผ ๋ชจ์•„ ์ด๋ฆ„์„ ๋ถ€์—ฌํ•ด ๋ฐ˜๋ณตํ˜ธ์ถœ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์ •์˜ํ•œ ํ”„๋กœ๊ทธ๋žจ ๊ตฌ์„ฑ ์š”์†Œ

ํ˜ธ์ถœ ์‹œ ์„œ๋ธŒ๋ฃจํ‹ด ์ง„์ž…ํ•˜๋ฉฐ ํ™œ์„ฑ ๋ ˆ์ฝ”๋“œ๊ฐ€ ์Šคํƒ์— ํ• ๋‹น๋˜๋Š”๋ฐ ์ด ์‹œ์ ์— ์„œ๋ธŒ๋ฃจํ‹ด ๋‚ด๋ถ€ ๋กœ์ปฌ ๋ณ€์ˆ˜๋“ฑ์ด ์ดˆ๊ธฐํ™” ๋œ๋‹ค. ๋ฐ˜ํ™˜ ํ•˜๋ฉด ํ™œ์„ฑ๋ ˆ์ฝ”๋“œ๊ฐ€ ์Šคํƒ์—์„œ ์‚ฌ๋ผ์ง€๋ฉฐ ๋ชจ๋“  ์ƒํƒœ๋ฅผ ๋‚ ๋ฆฐ๋‹ค.

 

์ฝ”๋ฃจํ‹ด์„ ์ข…ํ•ฉํ•˜๋ฉด ์„œ๋กœ ํ˜‘๋ ฅํ•ด ์‹คํ–‰์„ ์ฃผ๊ณ ๋ฐ›์œผ๋ฉฐ ์ž‘๋™ํ•˜๋Š” ์—ฌ๋Ÿฌ ์„œ๋ธŒ๋ฃจํ‹ด์„ ๋งํ•œ๋‹ค. 

 

์–ด๋–ค ํ•จ์ˆ˜ A๊ฐ€ ์‹คํ–‰๋˜๋‹ค๊ฐ€ ์ œ๋„ค๋ ˆ์ดํ„ฐ์ธ ์ฝ”๋ฃจํ‹ด B๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด A๊ฐ€ ์‹คํ–‰๋˜๋˜ ์Šค๋ ˆ๋“œ ์•ˆ์—์„œ ์ฝ”๋ฃจํ‹ด B์˜ ์‹คํ–‰์ด ์‹œ์ž‘๋œ๋‹ค. ์ฝ”๋ฃจํ‹ด B๋Š” ์‹คํ–‰์„ ์ง„ํ–‰ํ•˜๋‹ค๊ฐ€ ์‹คํ–‰์„ A์— ์–‘๋ณดํ•œ๋‹ค. A๋Š” ๋‹ค์‹œ ์ฝ”๋ฃจํ‹ด์„ ํ˜ธ์ถœํ–ˆ๋˜ ๋ฐ”๋กœ ๋‹ค์Œ ๋ถ€๋ถ„๋ถ€ํ„ฐ ์‹คํ–‰์„ ๊ณ„์† ์ง„ํ–‰ํ•˜๋‹ค๊ฐ€ ๋˜ ์ฝ”๋ฃจํ‹ด B๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

 

B์˜ ์ •์ฒด๊ฐ€ ์ผ๋ฐ˜์ ์ธ ํ•จ์ˆ˜๋ผ๋ฉด ๋กœ์ปฌ ๋ณ€์ˆ˜๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ฉด์„œ ์ฒ˜์Œ๋ถ€ํ„ฐ ์‹คํ–‰์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๊ฒ ์ง€๋งŒ, ์ฝ”๋ฃจํ‹ด์ด๋ฉด ์ด์ „์— ์‹คํ–‰์„ ์–‘๋ณดํ–ˆ๋˜ ์ง€์ ๋ถ€ํ„ฐ ์‹คํ–‰์„ ๊ณ„์†ํ•˜๊ฒŒ ๋œ๋‹ค.(์ง€์—ฐ)

 

์ฝ”ํ‹€๋ฆฐ์˜ ์ฝ”๋ฃจํ‹ด ์ง€์›: ์ผ๋ฐ˜์ ์ธ ์ฝ”๋ฃจํ‹ด

kotlinx.coroutines๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ฝ”๋ฃจํ‹ด ๋ช‡๊ฐ€์ง€๋ฅผ ๋จผ์ € ์•Œ์•„๋ณด์ž.

์šฐ์„  kotlinx.coroutines.core ๋ชจ๋“ˆ์— ์žˆ๋Š” ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๋“ค์ด๋‹ค.

  • kotlinx.coroutines.CoroutineScope.launch
    : ์ฝ”๋ฃจํ‹ด์„ Job์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ๋งŒ๋“ค์–ด์ง„ ์ฝ”๋ฃจํ‹ด์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์ฆ‰์‹œ ์‹คํ–‰๋œ๋‹ค. Job์„ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— cancel, join๊ณผ ๊ฐ™์€ ์กฐ์ž‘์ด ๊ฐ€๋Šฅํ•˜๋‹ค. launch์˜ ์ˆ˜์‹ ๊ฐ์ฒด๋Š” CoroutineScope๋‹ค.
    runBlocking์„ launch์˜ ์ˆ˜์‹ ๊ฐ์ฒด๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๋‚ด๋ถ€์ฝ”๋ฃจํ‹ด์ด ๋ชจ๋‘ ๋๋‚ ๋•Œ๊นŒ์ง€ ์“ฐ๋ ˆ๋“œ๋ฅผ ์ ์œ ํ•œ๋‹ค. 
    ์„œ๋กœ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด์ด ํ•œ ์Šค์ฝ”ํ”„์•ˆ์— ์žˆ์„๋•Œ yield๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ œ์–ด๊ถŒ์„ ๋„˜๊ธธ ์ˆ˜ ์žˆ๋‹ค. ์ด๋•Œ ์ œ์–ด๋ฅผ ๋„˜๊ธด ์ฝ”๋ฃจํ‹ด์ด delay ์ƒํƒœ๋ผ๋ฉด ๋‹ค์‹œ ์ œ์–ด๊ถŒ์ด ๋Œ์•„์˜ค๊ฒŒ ๋œ๋‹ค.
  • kotlinx.coroutines.CoroutineScope.async
    async๋Š” ์‚ฌ์‹ค์ƒ launch์™€ ๊ฐ™์€ ์ผ์„ ํ•œ๋‹ค. launch๊ฐ€ Job์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฐ˜๋ฉด async๋Š” Deffered๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. Deffered์™€ Job์˜ ์ฐจ์ด๋Š”, Job์€ ์•„๋ฌด ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์—†๋Š”๋ฐ Deffered๋Š” ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์žˆ๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์ด๋ผ๋Š” ์ ๊ณผ Deffered ์•ˆ์—๋Š” await() ํ•จ์ˆ˜๊ฐ€ ์ •์˜๋ผ ์žˆ๋‹ค๋Š” ์ ์ด๋‹ค. Deffered์˜ ํƒ€์ž… ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” ๋ฐ”๋กœ Deffered ์ฝ”๋ฃจํ‹ด์ด ๊ณ„์‚ฐ์„ ํ•˜๊ณ  ๋Œ๋ ค์ฃผ๋Š” ๊ฐ’์˜ ํƒ€์ž…์ด๋‹ค. ๋ชจ๋“  async ํ•จ์ˆ˜๋“ค์ด ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ์•ˆ์—์„œ ์‹คํ–‰๋˜๋Š” ๋น„๋™๊ธฐ ๋นŒ๋”๋ผ๊ณ  ์ดํ•ด ํ•  ์ˆ˜ ์žˆ๋‹ค.

์ฝ”๋ฃจํ‹ด ์ปจํ…์ŠคํŠธ์™€ ๋””์ŠคํŒจ์ฒ˜

CoroutineScope์—๋Š” CoroutineContext ํ•„๋“œ๊ฐ€ ์žˆ๋Š”๋ฐ ์‹ค์ œ๋กœ ์ฝ”๋ฃจํ‹ด์ด ์‹คํ–‰์ค‘์ธ ์—ฌ๋Ÿฌ ์ž‘์—…(Job)๊ณผ ๋””์ŠคํŒจ์ฒ˜๋ฅผ ์ €์žฅํ•˜๋Š” ์ผ์ข…์˜ ๋งต์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฝ”ํ‹€๋ฆฐ ๋Ÿฐํƒ€์ž„์€ ์ด๊ฑธ ์ด์šฉํ•ด ๋‹ค์Œ ์‹คํ–‰ํ•  ์ž‘์—…์„ ๋ฐฐ์น˜ํ•œ๋‹ค.

    launch { // context of the parent, main runBlocking coroutine
        println("main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
        println("Unconfined            : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher 
        println("Default               : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
        println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
    }

์ „๋‹ฌํ•˜๋Š” ์ปจํ…์ŠคํŠธ์— ๋”ฐ๋ผ ์„œ๋กœ ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ ์ฝ”๋ฃจํ‹ด์„ ์‹คํ–‰ํ•œ๋‹ค.

 

์ฝ”๋ฃจํ‹ด ๋นŒ๋”์™€ ์ผ์‹œ ์ค‘๋‹จ ํ•จ์ˆ˜

  • produce: ์ •ํ•ด์ง„ ์ฑ„๋„๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์ŠคํŠธ๋ฆผ์œผ๋กœ ๋ณด๋‚ด๋Š” ์ฝ”๋ฃจํ‹ด์„ ๋งŒ๋“ ๋‹ค. ์ด ํ•จ์ˆ˜๋Š” ReceiveChannel<>์„ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ ๊ทธ ์ฑ„๋„๋กœ๋ถ€ํ„ฐ ๋ฉ”์‹œ์ง€๋ฅผ ์ „๋‹ฌ๋ฐ›์•„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
  • actor: ์ •ํ•ด์ง„ ์ฑ„๋„๋กœ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ›์•„ ์ฒ˜๋ฆฌํ•˜๋Š” ์•กํ„ฐ๋ฅผ ์ฝ”๋ฃจํ‹ด์œผ๋กœ ๋งŒ๋“ ๋‹ค. ์ด ํ•จ์ˆ˜๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š” SendChannel<> ์ฑ„๋„์˜ send() ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์•กํ„ฐ์—๊ฒŒ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

delay์™€ yield๊ฐ™์€ ํ•จ์ˆ˜๋Š” ์ผ์‹œ์ค‘๋‹จ suspending ํ•จ์ˆ˜๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.
์ผ์‹œ์ค‘๋‹จ ํ•จ์ˆ˜๋“ค์ด๋‹ค

  • withContext - ๋‹ค๋ฅธ ์ปจํ…์ŠคํŠธ๋กœ ์ฝ”๋ฃจํ‹ด ์ „ํ™˜
  • withTimeout - ์ •ํ•ด์ง„์‹œ๊ฐ„์•ˆ์— ์‹คํ–‰๋˜์ง€์•Š์œผ๋ฉด ์˜ˆ์™ธ์ฒ˜๋ฆฌ
  • withTimeoutOrNull - null ๋ฐ˜ํ™˜
  • awaitAll - ๋ชจ๋“  ์ž‘์—…์˜ ์„ฑ๊ณต์ด ๋๋‚˜๋ฉด ์ด ํ•จ์ˆ˜๋„ ์„ฑ๊ณต. ํ•˜๋‚˜๋ผ๋„ ์˜ˆ์™ธ ๋ฐœ์ƒ์‹œ ์ด ํ•จ์ˆ˜๋„ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
  • joinAll - ๋ชจ๋“  ์ž‘์—…์ด ๋๋‚  ๋•Œ ๊นŒ์ง€ ํ˜„์žฌ ์ž‘์—… ์ผ์‹œ์ •์ง€

suspend ํ‚ค์›Œ๋“œ์™€ ์ฝ”ํ‹€๋ฆฐ์˜ ์ผ์‹œ ์ค‘๋‹จ ํ•จ์ˆ˜ ์ปดํŒŒ์ผ ๋ฐฉ๋ฒ•

์ผ์‹œ ์ค‘๋‹จ ํ•จ์ˆ˜ ์•ˆ์—์„œ yield()๋ฅผ ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ

  1. ์ฝ”๋ฃจํ‹ด์— ์ง„์ž…ํ•  ๋•Œ์™€ ์ฝ”๋ฃจํ‹ด์—์„œ ๋‚˜๊ฐˆ ๋•Œ ์ฝ”๋ฃจํ‹ด์ด ์‹คํ–‰ ์ค‘์ด๋˜ ์ƒํƒœ๋ฅผ ์ €์žฅํ•˜๊ณ  ๋ณต๊ตฌํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  2. ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ด๋˜ ์œ„์น˜๋ฅผ ์ €์žฅํ•˜๊ณ  ๋‹ค์‹œ ์ฝ”๋ฃจํ‹ด์ด ์žฌ๊ฐœ๋  ๋•Œ ํ•ด๋‹น ์œ„์น˜๋ถ€ํ„ฐ ์‹คํ–‰์„ ์žฌ๊ฐœํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  3. ๋‹ค์Œ์— ์–ด๋–ค ์ฝ”๋ฃจํ‹ด์„ ์‹คํ–‰ํ• ์ง€ ๊ฒฐ์ •ํ•œ๋‹ค. 

์ด ์„ธ ๊ฐ€์ง€ ๋™์ž‘ ์ค‘ ๋งˆ์ง€๋ง‰ ๋™์ž‘์€ ์ฝ”๋ฃจํ‹ด ์ปจํ…์ŠคํŠธ์— ์žˆ๋Š” ๋””์ŠคํŒจ์ฒ˜์— ์˜ํ•ด ์ˆ˜ํ–‰๋œ๋‹ค.

  • Dispatchers.Default: ๋””์ŠคํŒจ์ฒ˜์— ๋”ฐ๋กœ context๊ฐ€ ์ง€์ •๋˜์ง€์•Š์œผ๋ฉด ์ด๊ฒŒ ์‚ฌ์šฉ๋œ๋‹ค. ๊ณต์œ  ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์“ฐ๋ ˆ๋“œ์˜ ๊ณต์šฉ ํ’€์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— CPU๋ฆฌ์†Œ์Šค๊ฐ€ ๋งŽ์ด ํ•„์š”ํ•œ ์ž‘์—…์— ์ ํ•ฉํ•˜๋‹ค. 
  • Dispatchers.IO: ์š”์ฒญํ•  ๋•Œ ์ƒ๊ธฐ๋Š” ์“ฐ๋ ˆ๋“œ์˜ ๊ณต์œ  ํ’€์„ ์‚ฌ์šฉํ•˜๊ณ  ํŒŒ์ผ์ž…์ถœ๋ ฅ, ์†Œ์ผ“์ž…์ถœ๋ ฅ ์ฐจ๋‹จ๊ณผ ๊ฐ™์€ ์ž‘์—…์— ์‚ฌ์šฉ๋œ๋‹ค. ์ด๊ฑธ Offloading์ด๋ผ๊ณ  ํ•˜๋Š”๋ฐ CPU๊ฐ€ TCP/IP ํ”„๋กœ์„ธ์‹ฑํ•˜๋Š” ๊ฑธ ๋ง‰์•„์„œ ์„ฑ๋Šฅ์„ ์˜ฌ๋ฆฌ๋Š” ์ž‘์—…์ด๋‹ค. 
  • Dispatchers.Unconfined: ์ฝ”๋ฃจํ‹ด ๋นŒ๋”๊ฐ€ ๋ฐ˜ํ™˜๋˜๋Š” ์ฒซ ์ง€์ ๊นŒ์ง€ ์ฝ”๋ฃจํ‹ด์„ ์‹คํ–‰ํ•œ๋‹ค. ์ค‘๋‹จ๋œ ์ฝ”๋ฃจํ‹ด์€ ํ•ด๋‹น ์ค‘๋‹จ ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์“ฐ๋ ˆ๋“œ์—์„œ ์žฌ์‹œ์ž‘๋œ๋‹ค. ๋ฐ‘์— ์กฐ๊ธˆ ๋” ์ž์„ธํžˆ ์ •๋ฆฌํ•˜์˜€๋‹ค.

์ผ์‹œ ์ค‘๋‹จ ํ•จ์ˆ˜๋ฅผ ์ปดํŒŒ์ผํ•˜๋Š” ์ปดํŒŒ์ผ๋Ÿฌ๋Š” ์•ž์˜ ๋‘ ๊ฐ€์ง€ ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•ด ๋‚ด์•ผ ํ•œ๋‹ค. ์ด๋•Œ ์ฝ”ํ‹€๋ฆฐ์€ ์ปจํ‹ฐ๋‰ด์—์ด์…˜ ํŒจ์‹ฑ ์Šคํƒ€์ผ Continuation Passing Style(CPS)๊ณผ ์ƒํƒœ๊ธฐ๊ณ„ State Machine๋ฅผ ํ™œ์šฉํ•ด ์ฝ”๋“œ๋ฅผ ์ƒ์„ฑํ•ด๋‚ธ๋‹ค. CPS ๋ณ€ํ™˜์€ ํ”„๋กœ๊ทธ๋žจ์˜ ์‹คํ–‰ ์ค‘ ํŠน์ • ์‹œ์  ์ดํ›„์— ์ง„ํ–‰ํ•ด์•ผ ํ•˜๋Š” ๋‚ด์šฉ์„ ๋ณ„๋„์˜ ํ•จ์ˆ˜(์ด๋Ÿฐ ํ•จ์ˆ˜๋ฅผ Continuation ์ด๋ผ ๋ถ€๋ฅธ๋‹ค.)๋กœ ๋ฝ‘๊ณ  ๊ทธ ํ•จ์ˆ˜์—๊ฒŒ ํ˜„์žฌ ์‹œ์  ๊นŒ์ง€ ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ๋ฅผ ๋„˜๊ฒจ์„œ ์ฒ˜๋ฆฌํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ์†Œ์Šค์ฝ”๋“œ ๋ณ€ํ™˜ ๊ธฐ์ˆ ์ด๋‹ค.(์ผ์ข…์˜ ์ฝœ๋ฐฑ ์Šคํƒ€์ผ)

public static final Object example(int v, @NotNull Continuation var1)

suspend ํ•จ์ˆ˜๊ฐ€ ์ปดํŒŒ์ผ๋œ ๋ชจ์Šต์ด๋‹ค. Continuation์œผ๋กœ ๋ฐ˜ํ™˜๊ฐ’์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฑธ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

 

"๋Œ“๊ธ€, ๊ณต๊ฐ ๋ฒ„ํŠผ ํ•œ ๋ฒˆ์”ฉ ๋ˆ„๋ฅด๊ณ  ๊ฐ€์ฃผ์‹œ๋ฉด ํฐ ํž˜์ด ๋ฉ๋‹ˆ๋‹ค"
๋ฐ˜์‘ํ˜•

'๐Ÿ“– > ์ฝ”ํ‹€๋ฆฐ ์ธ ์•ก์…˜๐Ÿ“–' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

Kotlin in action(8)  (0) 2023.10.10
Kotlin in action(7)  (0) 2023.10.10
Kotlin in action(6)  (0) 2023.10.10
Kotlin in action(5)  (0) 2023.10.10
Kotlin in action(4)  (0) 2023.10.10
COMMENT