2026๋…„ Kotlin Multiplatform: ์•ˆ๋“œ๋กœ์ด๋“œ์™€ iOS ์ฝ”๋“œ ๊ณต์œ 

2026๋…„ Kotlin Multiplatform์ด Android์™€ iOS์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๊ณต์œ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค. Gradle ์„ค์ •, expect/actual, Ktor, Compose Multiplatform, KMP ๋ฉด์ ‘ ์งˆ๋ฌธ๊นŒ์ง€ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

2026๋…„ Kotlin Multiplatform์œผ๋กœ Android์™€ iOS ์•ฑ ๊ฐ„ ์ฝ”๋“œ๋ฅผ ๊ณต์œ ํ•˜๋Š” ๋ชจ์Šต

Kotlin Multiplatform(KMP)์€ ํ•˜๋‚˜์˜ Kotlin ์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ Android์™€ iOS ์•ฑ์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ํ•จ๊ป˜ ๊ตฌ๋™ํ•˜๋ฉด์„œ๋„, ๊ฐ ํ”Œ๋žซํผ์ด ์™„์ „ํ•œ ๋„ค์ดํ‹ฐ๋ธŒ UI์™€ ์„ฑ๋Šฅ, ๊ทธ๋ฆฌ๊ณ  ์ž์ฒด SDK ์ ‘๊ทผ์„ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๊ฒŒ ํ•ด ์ค๋‹ˆ๋‹ค. 2026๋…„์˜ KMP๋Š” ๋” ์ด์ƒ ์‹คํ—˜์ด ์•„๋‹™๋‹ˆ๋‹ค. ์•ˆ์ • ๋ฒ„์ „์ด๋ฉฐ Google์ด ๊ณต์‹์ ์œผ๋กœ ์ง€์›ํ•˜๊ณ , Netflix, McDonald's, Forbes, Cash App์˜ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ์‹ค์ œ๋กœ ๋™์ž‘ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ธ€์—์„œ๋Š” ์ฝ”๋“œ ๊ณต์œ ๊ฐ€ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€, ๊ณต์œ  ๋ชจ๋“ˆ์„ ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ•˜๋Š”์ง€, ๊ทธ๋ฆฌ๊ณ  KMP ๋ฉด์ ‘์— ์•ž์„œ ์ดํ•ดํ•ด ๋‘˜ ๋งŒํ•œ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„๋ฅผ ๊นŠ์ด ์žˆ๊ฒŒ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

KMP๊ฐ€ ์‹ค์ œ๋กœ ๊ณต์œ ํ•˜๋Š” ๊ฒƒ

Kotlin Multiplatform์ด ๊ณต์œ ํ•˜๋Š” ๊ฒƒ์€ ์ปดํŒŒ์ผ๋œ ๋กœ์ง์ด์ง€ ๋Ÿฐํƒ€์ž„์ด ์•„๋‹™๋‹ˆ๋‹ค. ๊ณตํ†ต Kotlin์€ Android์šฉ์œผ๋กœ๋Š” JVM ๋ฐ”์ดํŠธ์ฝ”๋“œ๋กœ, iOS์šฉ์œผ๋กœ๋Š” (LLVM์„ ํ†ตํ•ด) ๋„ค์ดํ‹ฐ๋ธŒ ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ ์ปดํŒŒ์ผ๋ฉ๋‹ˆ๋‹ค. JavaScript ๋ธŒ๋ฆฌ์ง€๋„, ๋‚ด์žฅ ์›น๋ทฐ๋„, ๋ฆฌํ”Œ๋ ‰์…˜ ๊ณ„์ธต๋„ ์—†๊ธฐ ๋•Œ๋ฌธ์— ๊ณต์œ  ์ฝ”๋“œ๋Š” ์–‘์ชฝ ํ”Œ๋žซํผ์—์„œ ๋„ค์ดํ‹ฐ๋ธŒ ์†๋„๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.

Kotlin Multiplatform์ด ๋„ค์ดํ‹ฐ๋ธŒ ์„ฑ๋Šฅ์„ ํฌ์ƒํ•˜์ง€ ์•Š๊ณ  ์ฝ”๋“œ๋ฅผ ๊ณต์œ ํ•˜๋Š” ๋ฐฉ์‹

ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜์€ ์†Œ์Šค ์„ธํŠธ์˜ ๊ณ„์ธต ๊ตฌ์กฐ์ž…๋‹ˆ๋‹ค. commonMain์—๋Š” ๋„คํŠธ์›Œํ‚น, ์ง๋ ฌํ™”, ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™, ๋ทฐ ๋ชจ๋ธ์ฒ˜๋Ÿผ ํ”Œ๋žซํผ์— ์ข…์†๋˜์ง€ ์•Š๋Š” Kotlin์„ ๋‘ก๋‹ˆ๋‹ค. androidMain๊ณผ iosMain ๊ฐ™์€ ํ”Œ๋žซํผ ์†Œ์Šค ์„ธํŠธ์—๋Š” ํ”Œ๋žซํผ API๋ฅผ ๋‹ค๋ฃจ๋Š” ์ฝ”๋“œ๋ฅผ ๋‘ก๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ๋Ÿฌ๋Š” Android์šฉ์œผ๋กœ commonMain๊ณผ androidMain์—์„œ JVM ์•„ํ‹ฐํŒฉํŠธ๋ฅผ, iOS์šฉ์œผ๋กœ commonMain๊ณผ iosMain์—์„œ ๋„ค์ดํ‹ฐ๋ธŒ ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

2026๋…„์˜ Kotlin Multiplatform์€ ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›์—์„œ ์ง„์ •์œผ๋กœ ์ด๋“์„ ๋ณด๋Š” ๊ฒƒ, ์ฆ‰ ๋ชจ๋ธ, API ํด๋ผ์ด์–ธํŠธ, ์บ์‹ฑ, ๊ฒ€์ฆ์„ ๊ณต์œ ํ•˜๋Š” ๋ฐ ๋ฌด๊ฒŒ๋ฅผ ๋‘ก๋‹ˆ๋‹ค. UI๋Š” Compose Multiplatform์œผ๋กœ ๊ณต์œ ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ํ™”๋ฉด ๋‹จ์œ„๋กœ SwiftUI๋ฅผ ์‚ฌ์šฉํ•ด ์™„์ „ํ•œ ๋„ค์ดํ‹ฐ๋ธŒ๋กœ ๋‚จ๊ฒจ ๋‘˜ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. "๋กœ์ง์€ ๊ณต์œ ํ•˜๊ณ  UI๋Š” ์„ ํƒํ•œ๋‹ค"๋Š” ์ด ๋ชจ๋ธ์ด์•ผ๋ง๋กœ ๊ธฐ์กด ์•ฑ์„ ๋‹ค์‹œ ์ž‘์„ฑํ•˜์ง€ ์•Š๊ณ ๋„ ํŒ€์ด KMP๋ฅผ ๋„์ž…ํ•˜๋Š” ์ด์œ ์ž…๋‹ˆ๋‹ค.

Kotlin Multiplatform์€ ํ•˜๋‚˜์˜ ๊ณต์œ  ๋ชจ๋“ˆ์„ ์—ฌ๋Ÿฌ ํƒ€๊นƒ(JVM ์œ„์˜ Android, ๋„ค์ดํ‹ฐ๋ธŒ๋กœ์„œ์˜ iOS, ๊ทธ๋ฆฌ๊ณ  ๋ฐ์Šคํฌํ†ฑ๊ณผ ์›น)์œผ๋กœ ์ปดํŒŒ์ผํ•˜๋Š” Kotlin ๊ธฐ์ˆ ์ž…๋‹ˆ๋‹ค. ๋•๋ถ„์— ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ํ•œ ๋ฒˆ๋งŒ ์ž‘์„ฑํ•˜๋ฉด ๋˜๊ณ , ๊ฐ ํ”Œ๋žซํผ์€ ๋„ค์ดํ‹ฐ๋ธŒ UI์™€ ์™„์ „ํ•œ SDK ์ ‘๊ทผ์„ ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ Google์€ ์ด๋ฅผ ์ผ๊ธ‰ ์˜ต์…˜์œผ๋กœ ์ง€์›ํ•˜๋ฉฐ, Room, DataStore, ViewModel, Paging ๊ฐ™์€ Jetpack ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ KMP ์•„ํ‹ฐํŒฉํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๊ณต์œ  ๋ชจ๋“ˆ์— ๋ฌด์—‡์„ ๋„ฃ์„์ง€ ๊ฒฐ์ •ํ•˜๋Š” ์ผ์€ KMP ํ”„๋กœ์ ํŠธ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์•„ํ‚คํ…์ฒ˜ ํŒ๋‹จ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜ ํ‘œ๋Š” 2026๋…„์— ๋Œ€๋‹ค์ˆ˜ ํ”„๋กœ๋•์…˜ ํŒ€์ด ๊ฒฝ๊ณ„๋ฅผ ์–ด๋””์— ๊ธ‹๋Š”์ง€๋ฅผ ๋ณด์—ฌ ์ค๋‹ˆ๋‹ค. ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต ์•„๋ž˜์˜ ๋ชจ๋“  ๊ฒƒ์€ ๊ณต์œ ์˜ ์œ ๋ ฅํ•œ ํ›„๋ณด์ด๋ฉฐ, ํ”Œ๋žซํผ์˜ UX ๊ด€๋ก€์— ๋‹ฟ๋Š” ๊ฒƒ์€ ๋Œ€๊ฐœ ๋„ค์ดํ‹ฐ๋ธŒ๋กœ ๋‚จ์Šต๋‹ˆ๋‹ค.

| ๊ณ„์ธต | commonMain์—์„œ ๊ณต์œ ? | ์ด์œ  | |-------|---------------------|--------| | ๋ฐ์ดํ„ฐ ๋ชจ๋ธ๊ณผ DTO | ์˜ˆ | ์–‘์ชฝ ํ”Œ๋žซํผ์—์„œ ๋™์ผํ•œ API ๊ณ„์•ฝ | | ๋„คํŠธ์›Œํ‚น๊ณผ ์บ์‹ฑ | ์˜ˆ | HTTP ์Šคํƒ ํ•˜๋‚˜, ๋ฒ„๊ทธ ํ‘œ๋ฉด ํ•˜๋‚˜ | | ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™๊ณผ ๊ฒ€์ฆ | ์˜ˆ | ๋„๋ฉ”์ธ์€ OS๋งˆ๋‹ค ๋‹ค๋ฅด์ง€ ์•Š์Œ | | ๋ทฐ ๋ชจ๋ธ๊ณผ ์ƒํƒœ | ๋Œ€๊ฐœ | Compose์™€ SwiftUI ๋ชจ๋‘ ๊ณต์œ  ์ƒํƒœ๋ฅผ ์†Œ๋น„ | | UI ๋ Œ๋”๋ง | ์„ ํƒ | Compose๋กœ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ํ™”๋ฉด๋ณ„ ๋„ค์ดํ‹ฐ๋ธŒ | | ํ”Œ๋žซํผ API(์นด๋ฉ”๋ผ, ์ƒ์ฒด ์ธ์ฆ) | ์•„๋‹ˆ์˜ค | ๋Œ€์‹  expect/actual๋กœ ๋…ธ์ถœ |

์–ป๋Š” ์ด๋“์€ ์•ฑ์ด ์ด ํ‘œ์˜ ์•„๋ž˜์ชฝ ์ ˆ๋ฐ˜์„ ์–ผ๋งˆ๋‚˜ ์ฐจ์ง€ํ•˜๋А๋ƒ์— ๋น„๋ก€ํ•ด ์ปค์ง‘๋‹ˆ๋‹ค. UI๊ฐ€ ์–‡๊ณ  ๋ฐ์ดํ„ฐ ์ค‘์‹ฌ์ธ ์ œํ’ˆ์ผ์ˆ˜๋ก ๊ณต์œ ๋Ÿ‰์ด ๋งŽ๊ณ , ๊ณ ๋„๋กœ ๋งž์ถคํ™”๋˜๊ณ  ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ค‘์‹ฌ์ธ ์•ฑ์ผ์ˆ˜๋ก ๊ณต์œ ๋Ÿ‰์ด ์ ์Šต๋‹ˆ๋‹ค. ์–ด๋А ์ชฝ ๊ทน๋‹จ๋„ ํ‹€๋ฆฐ ๊ฒƒ์€ ์•„๋‹™๋‹ˆ๋‹ค. ๊ทธ๋ž˜์„œ KMP๋Š” ์ „๋ถ€ ์•„๋‹ˆ๋ฉด ์ „๋ฌด ์‹์˜ ์žฌ์ž‘์„ฑ๋ณด๋‹ค ์ ์ง„์  ๋„์ž…์— ์ž˜ ๋งž์Šต๋‹ˆ๋‹ค.

Gradle์—์„œ ๊ณต์œ  ๋ชจ๋“ˆ ๊ตฌ์„ฑํ•˜๊ธฐ

KMP ๋ชจ๋“ˆ์€ Kotlin DSL๋กœ ํƒ€๊นƒ๊ณผ ์†Œ์Šค ์„ธํŠธ๋ฅผ ์„ ์–ธํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ build.gradle.kts๋Š” Android์™€ ์„ธ ๊ฐ€์ง€ iOS ์•„ํ‚คํ…์ฒ˜(์‹ค๊ธฐ๊ธฐ, Intel ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, Apple Silicon ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ)๋ฅผ ์„ค์ •ํ•˜๊ณ , iOS ์ถœ๋ ฅ์„ Shared๋ผ๋Š” ์ด๋ฆ„์˜ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

shared/build.gradle.ktskotlin
plugins {
    kotlin("multiplatform")
    kotlin("plugin.serialization")
    id("com.android.library")
}

kotlin {
    // Android target compiles commonMain + androidMain to JVM bytecode
    androidTarget()

    // Three iOS targets: device, Intel sim, Apple Silicon sim
    listOf(iosArm64(), iosX64(), iosSimulatorArm64()).forEach { target ->
        target.binaries.framework {
            baseName = "Shared"   // becomes "import Shared" in Swift
            isStatic = true
        }
    }

    sourceSets {
        commonMain.dependencies {
            implementation("io.ktor:ktor-client-core:3.3.0")
            implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2")
        }
        androidMain.dependencies {
            implementation("io.ktor:ktor-client-okhttp:3.3.0")
        }
        iosMain.dependencies {
            implementation("io.ktor:ktor-client-darwin:3.3.0")
        }
    }
}

๊ฐ ํƒ€๊นƒ์€ ํ”Œ๋žซํผ๋ณ„ Ktor ์—”์ง„(Android์—์„œ๋Š” okhttp, iOS์—์„œ๋Š” darwin)์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ฐ˜๋ฉด, ๊ณต์œ  ์ฝ”๋“œ๋Š” ๊ณตํ†ต ktor-client-core API์—๋งŒ ์˜์กดํ•ฉ๋‹ˆ๋‹ค. ์„ค์ • ๊ฐ€๋Šฅํ•œ ์˜ต์…˜ ์ „์ฒด๋Š” Kotlin Multiplatform ๊ณต์‹ ๋ฌธ์„œ์— ์ •๋ฆฌ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

expect์™€ actual๋กœ ํ”Œ๋žซํผ๋ณ„ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ

์ผ๋ถ€ ๋กœ์ง์€ ํ”Œ๋žซํผ API๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— commonMain์— ๋‘˜ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. expect/actual ๋ฉ”์ปค๋‹ˆ์ฆ˜์€ ๊ณตํ†ต ์ฝ”๋“œ์—์„œ ํ˜•ํƒœ๋ฅผ ์„ ์–ธํ•˜๊ณ  ํƒ€๊นƒ๋ณ„๋กœ ๊ตฌ์ฒด์ ์ธ ๊ตฌํ˜„์„ ์ œ๊ณตํ•˜๋ฉฐ, ์ปดํŒŒ์ผ ์‹œ์ ์— ํ•ด๊ฒฐ๋˜์–ด ๋Ÿฐํƒ€์ž„ ๋น„์šฉ์ด ์ „ํ˜€ ์—†์Šต๋‹ˆ๋‹ค.

commonMain/kotlin/Platform.ktkotlin
// commonMain declares what it needs but cannot implement here
expect class Platform() {
    val name: String
    val osVersion: String
}

// Shared logic can now depend on Platform freely
fun analyticsUserAgent(): String {
    val platform = Platform()
    return "SharpSkill/" + platform.name + " " + platform.osVersion
}

Android ๊ตฌํ˜„์€ Build ํด๋ž˜์Šค์—์„œ, iOS ๊ตฌํ˜„์€ UIDevice์—์„œ ๊ฐ’์„ ์ฝ์Šต๋‹ˆ๋‹ค. ๋‘˜ ๋‹ค ๊ฐ™์€ ๊ณ„์•ฝ์„ ์ถฉ์กฑํ•˜๋ฏ€๋กœ commonMain์€ ์ž์‹ ์ด ์–ด๋А ์ชฝ๊ณผ ๋Œ€ํ™”ํ•˜๋Š”์ง€ ๊ฒฐ์ฝ” ์•Œ์ง€ ๋ชปํ•ฉ๋‹ˆ๋‹ค.

androidMain/kotlin/Platform.ktkotlin
import android.os.Build

// actual supplies the Android-specific implementation
actual class Platform actual constructor() {
    actual val name: String = "Android"
    actual val osVersion: String = Build.VERSION.RELEASE
}
iosMain/kotlin/Platform.ktkotlin
import platform.UIKit.UIDevice

// actual uses UIKit directly, no Objective-C bridging code required
actual class Platform actual constructor() {
    actual val name: String = "iOS"
    actual val osVersion: String = UIDevice.currentDevice.systemVersion
}

Kotlin/Native๋Š” iOS SDK ์ „์ฒด์— ๋Œ€ํ•œ ํƒ€์ž… ์ง€์ • ๋ฐ”์ธ๋”ฉ์„ ํ•จ๊ป˜ ์ œ๊ณตํ•˜๋ฏ€๋กœ UIDevice, NSUserDefaults, Foundation ํƒ€์ž…์„ ์†์œผ๋กœ ์ž‘์„ฑํ•œ ์—ฐ๊ฒฐ ์ฝ”๋“œ ์—†์ด Kotlin์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋„คํŠธ์›Œํ‚น๊ณผ ์ง๋ ฌํ™” ๋กœ์ง ๊ณต์œ ํ•˜๊ธฐ

๋„คํŠธ์›Œํ‚น์€ API ๊ณ„์•ฝ์ด ์–‘์ชฝ ํ”Œ๋žซํผ์—์„œ ๋™์ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ณต์œ  ๊ฐ€์น˜๊ฐ€ ๊ฐ€์žฅ ๋†’์€ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ํ•˜๋‚˜์˜ Ktor ํด๋ผ์ด์–ธํŠธ์™€ kotlinx.serialization์ด ๋‘ ๊ฐœ์˜ ๋ณ‘๋ ฌ ๊ตฌํ˜„์„ ๋Œ€์ฒดํ•ฉ๋‹ˆ๋‹ค. suspend ํ•จ์ˆ˜๋Š” Kotlin ์ฝ”๋ฃจํ‹ด์— ์˜์กดํ•˜๋ฉฐ, ์–‘์ชฝ์—์„œ async/await๋กœ ๊น”๋”ํ•˜๊ฒŒ ๋Œ€์‘๋ฉ๋‹ˆ๋‹ค.

commonMain/kotlin/data/InterviewRepository.ktkotlin
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.Serializable

// One serializable model, zero duplication across platforms
@Serializable
data class Question(val id: String, val prompt: String, val difficulty: String)

// One HttpClient, one JSON parser, one repository for both apps
class InterviewRepository {
    private val client = HttpClient {
        install(ContentNegotiation) { json() }
    }

    suspend fun fetchQuestions(tech: String): List<Question> =
        client.get("https://api.sharpskill.dev/questions/" + tech).body()
}

์ด ๋ฆฌํฌ์ง€ํ† ๋ฆฌ์™€ ๊ทธ ์žฌ์‹œ๋„ ๋กœ์ง, ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์€ ํ•œ ๋ฒˆ๋งŒ ์ž‘์„ฑ๋ฉ๋‹ˆ๋‹ค. ํŒŒ์„œ์—์„œ ๊ณ ์นœ ๋ฒ„๊ทธ๋Š” Android์™€ iOS์—์„œ ๋™์‹œ์— ๊ณ ์ณ์ง€๋ฉฐ, ๋ฐ”๋กœ ์ด ์ง€์ ์—์„œ KMP๊ฐ€ ๋„์ž… ๋น„์šฉ์„ ํšŒ์ˆ˜ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ™์€ ํŒจํ„ด์ด SQLDelight๋ฅผ ์ด์šฉํ•œ ๊ณต์œ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค, ๊ณต์œ  ํ‚ค-๊ฐ’ ์ €์žฅ์†Œ, Koin์„ ์ด์šฉํ•œ ๊ณต์œ  ์˜์กด์„ฑ ์ฃผ์ž…์œผ๋กœ๋„ ํ™•์žฅ๋˜๋ฏ€๋กœ, ๋ฐ์ดํ„ฐ ๊ณ„์ธต ์ „์ฒด๋ฅผ UI๊ฐ€ ์˜์กดํ•˜๋Š” ์ธํ„ฐํŽ˜์ด์Šค ๋’ค์˜ commonMain์— ๋‘˜ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ”ํ•œ ์‹ค์ˆ˜๋Š” ๋„ˆ๋ฌด ์ด๋ฅธ ์‹œ์ ์— ๋„ˆ๋ฌด ๋งŽ์€ ๊ฒƒ์„ ๊ณต์œ ํ•˜๋ ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์‹ค์šฉ์ ์ธ ๋ฐฉ๋ฒ•์€ ๋จผ์ € ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ํ•˜๋‚˜๋กœ ์‹œ์ž‘ํ•ด ์–‘์ชฝ ํ”Œ๋žซํผ์—์„œ ๋นŒ๋“œ์™€ CI ํŒŒ์ดํ”„๋ผ์ธ์„ ๊ฒ€์ฆํ•œ ๋’ค, ์ž์‹ ๊ฐ์ด ๋ถ™์„์ˆ˜๋ก ๊ธฐ๋Šฅ์„ ํ•˜๋‚˜์”ฉ ์˜ฎ๊ธฐ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

Android ๋ฉด์ ‘ ์ค€๋น„๊ฐ€ ๋˜์…จ๋‚˜์š”?

์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ, flashcards, ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์—ฐ์Šตํ•˜์„ธ์š”.

Compose Multiplatform์œผ๋กœ ๊ณต์œ  UI ๊ตฌ์ถ•ํ•˜๊ธฐ

Compose Multiplatform์€ 2025๋…„ 5์›”์— iOS์—์„œ ์•ˆ์ • ๋ฒ„์ „(1.8.0)์— ๋„๋‹ฌํ–ˆ๊ณ , 2026๋…„ ์ค‘๋ฐ˜์—๋Š” ๋„ค์ดํ‹ฐ๋ธŒ ํ…์ŠคํŠธ ์ž…๋ ฅ๊ณผ ๊ธฐ๋ณธ ํ™œ์„ฑํ™”๋œ ๋™์‹œ ๋ Œ๋”๋ง์„ ๊ฐ–์ถ˜ 1.11.0์— ์ด๋ฅด๋ €์Šต๋‹ˆ๋‹ค. commonMain์— ์žˆ๋Š” @Composable์€ ์–‘์ชฝ ํ”Œ๋žซํผ์—์„œ ๋ Œ๋”๋ง๋˜๋ฉฐ, ๊ณต์œ  ๋ทฐ ๋ชจ๋ธ์€ MVVM ๋˜๋Š” MVI ์•„ํ‚คํ…์ฒ˜์™€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์–ด์šธ๋ฆฝ๋‹ˆ๋‹ค.

commonMain/kotlin/ui/QuestionList.ktkotlin
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.padding

// This list renders identically on Android and iOS
@Composable
fun QuestionList(questions: List<Question>) {
    LazyColumn {
        items(questions) { question ->
            Card(modifier = Modifier.padding(8.dp)) {
                Text(text = question.prompt, modifier = Modifier.padding(16.dp))
            }
        }
    }
}

UI ๊ณต์œ ๋Š” ์„ ํƒ ์‚ฌํ•ญ์ด๋ฉฐ ํ™”๋ฉด ๋‹จ์œ„์ž…๋‹ˆ๋‹ค. ํŒ€์€ Compose Multiplatform์œผ๋กœ ์ธํ„ฐํŽ˜์ด์Šค ์ „์ฒด๋ฅผ ๊ณต์œ ํ•  ์ˆ˜๋„, ์•ฑ ์ „์ฒด๋ฅผ SwiftUI๋กœ ์œ ์ง€ํ•œ ์ฑ„ ๋กœ์ง๋งŒ ๊ณต์œ ํ•  ์ˆ˜๋„, ๋‘˜์„ ์„ž์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์œ„์ ฏ์ด๋‚˜ App Clip์ฒ˜๋Ÿผ ํ”Œ๋žซํผ๋ณ„ UX์— ์˜์กดํ•˜๋Š” ํ™”๋ฉด์€ ๋ณดํ†ต ๋„ค์ดํ‹ฐ๋ธŒ๋กœ ๋‚จ๊น๋‹ˆ๋‹ค.

iOS์—์„œ Swift๋กœ ๊ณต์œ  ์ฝ”๋“œ ์‚ฌ์šฉํ•˜๊ธฐ

๊ณต์œ  ๋ชจ๋“ˆ์€ Swift๊ฐ€ ์—ฌ๋А ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ฒ˜๋Ÿผ ์ž„ํฌํŠธํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ์ปดํŒŒ์ผ๋ฉ๋‹ˆ๋‹ค. ์—ญ์‚ฌ์ ์œผ๋กœ ๋งˆ์ฐฐ์ด ๋˜์—ˆ๋˜ ๋ถ€๋ถ„์€ Kotlin์˜ suspend ํ•จ์ˆ˜์™€ Flow๊ฐ€ ์–ด์ƒ‰ํ•œ ์ฝœ๋ฐฑ API๋กœ ๋“œ๋Ÿฌ๋‚œ๋‹ค๋Š” ์ ์ด์—ˆ์Šต๋‹ˆ๋‹ค. Touchlab์˜ SKIE๋Š” ๊ด€์šฉ์ ์ธ Swift๋ฅผ ์ƒ์„ฑํ•ด ์ด๋ฅผ ํ•ด๊ฒฐํ•ฉ๋‹ˆ๋‹ค. suspend๋Š” ๋„ค์ดํ‹ฐ๋ธŒ async/await๊ฐ€ ๋˜๊ณ  Flow๋Š” AsyncSequence๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

iosApp/ContentView.swiftswift
import SwiftUI
import Shared   // the KMP framework, baseName = "Shared"

struct ContentView: View {
    @State private var questions: [Question] = []
    private let repository = InterviewRepository()

    var body: some View {
        List(questions, id: \.id) { question in
            Text(question.prompt)
        }
        .task {
            // With SKIE, the Kotlin suspend fun is a Swift async fun
            questions = (try? await repository.fetchQuestions(tech: "android")) ?? []
        }
    }
}

iOS ํŒ€์€ ํ‰๋ฒ”ํ•œ Swift์™€ SwiftUI๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋“ค์€ ๊ณต์œ  ๋ชจ๋ธ๊ณผ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ฅผ ๋„ค์ดํ‹ฐ๋ธŒ Swift ํŒจํ‚ค์ง€์™€ ๋˜‘๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, KMP๋Š” iOS ์ฝ”๋“œ๋ฒ ์ด์Šค์˜ ๋Œ€๋ถ€๋ถ„์—์„œ ๋ณด์ด์ง€ ์•Š๋Š” ์ƒํƒœ๋กœ ๋‚จ์Šต๋‹ˆ๋‹ค.

๋ฉด์ ‘์—์„œ ์–ธ๊ธ‰ํ•  ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š” ๊ฑฐ์นœ ๋ถ€๋ถ„์ด ์•„์ง ๋‘ ๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒซ์งธ๋Š” ๋นŒ๋“œ ์‹œ๊ฐ„์ž…๋‹ˆ๋‹ค. Kotlin/Native ์ปดํŒŒ์ผ์€ ์ˆœ์ˆ˜ Swift ๋นŒ๋“œ๋ณด๋‹ค ๋А๋ฆฌ์ง€๋งŒ, Gradle ๋นŒ๋“œ ์บ์‹œ์™€ ๋ฏธ๋ฆฌ ๋นŒ๋“œ๋œ XCFramework ๋ฐฐํฌ๊ฐ€ ์ผ์ƒ์ ์ธ ๋น„์šฉ์„ ์™„ํ™”ํ•ฉ๋‹ˆ๋‹ค. ๋‘˜์งธ๋Š” Swift์—์„œ Kotlin์œผ๋กœ ๋„˜์–ด๊ฐ€๋Š” ๊ฒฝ๊ณ„์—์„œ์˜ ๋””๋ฒ„๊น…์ž…๋‹ˆ๋‹ค. ๊ฐœ์„ ๋˜๊ณ  ์žˆ์ง€๋งŒ ํ•˜๋‚˜์˜ ์–ธ์–ด์— ๋จธ๋ฌด๋ฅผ ๋•Œ๋งŒํผ ์•„์ง ๋งค๋„๋Ÿฝ์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค. ์ด ๋˜ํ•œ ํŒ€์ด ๊ณต์œ  ๋ฒ”์œ„๋ฅผ ๋ฌด์งˆ์„œํ•˜๊ฒŒ ๋„“ํžˆ๊ธฐ๋ณด๋‹ค ์˜๋„์ ์œผ๋กœ ์œ ์ง€ํ•˜๋Š” ๋˜ ํ•˜๋‚˜์˜ ์ด์œ ์ž…๋‹ˆ๋‹ค.

์˜ˆ์ƒ๋˜๋Š” Kotlin Multiplatform ๋ฉด์ ‘ ์งˆ๋ฌธ

KMP ๋ฉด์ ‘ ์งˆ๋ฌธ์€ Android์™€ ๋ชจ๋ฐ”์ผ ์ง๋ฌด์—์„œ ์ ์  ๋” ์ž์ฃผ ๋“ฑ์žฅํ•˜๋ฏ€๋กœ, ํญ๋„“์€ Android ๋ฉด์ ‘ ์ค€๋น„์™€ ํ•จ๊ป˜ ๋ฐ˜๋ณต๋˜๋Š” ๋ช‡ ๊ฐ€์ง€๋ฅผ ์—ฐ์Šตํ•ด ๋‘˜ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

  • expect/actual์€ ์ธํ„ฐํŽ˜์ด์Šค์™€ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฆ…๋‹ˆ๊นŒ? expect/actual์€ ๋Ÿฐํƒ€์ž„ ๋””์ŠคํŒจ์น˜ ์—†์ด ํƒ€๊นƒ๋ณ„๋กœ ์ปดํŒŒ์ผ ์‹œ์ ์— ํ•ด๊ฒฐ๋˜๋ฉฐ, ํด๋ž˜์Šค, ํ•จ์ˆ˜, ํ”„๋กœํผํ‹ฐ, typealias๋ฅผ ๋’ท๋ฐ›์นจํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ธํ„ฐํŽ˜์ด์Šค๋Š” ๋Ÿฐํƒ€์ž„์— ๋™์ ์œผ๋กœ ํ•ด๊ฒฐ๋ฉ๋‹ˆ๋‹ค. ํ”Œ๋žซํผ API์—๋Š” expect/actual์„, ๊ณต์œ  ์ฝ”๋“œ ๋‚ด๋ถ€์˜ ๋‹คํ˜•์„ฑ์—๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • Kotlin/Native๋Š” ๋ฉ”๋ชจ๋ฆฌ์™€ ์Šค๋ ˆ๋”ฉ์„ ์–ด๋–ป๊ฒŒ ๋‹ค๋ฃน๋‹ˆ๊นŒ? Kotlin 1.7.20๋ถ€ํ„ฐ ์ƒˆ ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ์ž๊ฐ€ ๊ธฐ์กด์˜ ๊ฐ์ฒด ๋™๊ฒฐ ๋ชจ๋ธ์„ ์—†์•ด๊ธฐ ๋•Œ๋ฌธ์—, ๊ณต์œ ๋˜๋Š” ๊ฐ€๋ณ€ ์ƒํƒœ์™€ ์ฝ”๋ฃจํ‹ด์ด JVM์—์„œ์™€ ๊ฑฐ์˜ ๊ฐ™์€ ๋ฐฉ์‹์œผ๋กœ ์Šค๋ ˆ๋“œ๋ฅผ ๋„˜๋‚˜๋“ค๋ฉฐ ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ์กด ์•ฑ์ด KMP๋ฅผ ์ ์ง„์ ์œผ๋กœ ๋„์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๊นŒ? ์˜ˆ. ๊ณต์œ  ๋ชจ๋“ˆ์€ Android์šฉ์œผ๋กœ๋Š” AAR, iOS์šฉ์œผ๋กœ๋Š” XCFramework๋กœ ๋ฐฐํฌ๋˜๋ฏ€๋กœ ์žฌ์ž‘์„ฑ ์—†์ด ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋‚˜ ๊ธฐ๋Šฅ ํ•˜๋‚˜์”ฉ ์ด์ „ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • Compose Multiplatform ๋Œ€์‹  UI๋ฅผ ๋„ค์ดํ‹ฐ๋ธŒ๋กœ ๋‚จ๊ฒจ์•ผ ํ•  ๋•Œ๋Š” ์–ธ์ œ์ž…๋‹ˆ๊นŒ? ํ™”๋ฉด์ด ํ”Œ๋žซํผ๋ณ„ UX์— ํฌ๊ฒŒ ์˜์กดํ•  ๋•Œ, ๋˜๋Š” iOS ํŒ€์ด ํ”„๋ ˆ์  ํ…Œ์ด์…˜ ๊ณ„์ธต์„ ์†Œ์œ ํ•˜๊ณ  ์ง์ ‘ ์ œ์–ดํ•˜๊ธฐ๋ฅผ ์›ํ•  ๋•Œ์ž…๋‹ˆ๋‹ค.
2026๋…„์˜ ํˆด์ฒด์ธ

K2 ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ๊ฐ–์ถ˜ Kotlin 2.3, ๋„คํŠธ์›Œํ‚น์—๋Š” Ktor 3.x, ๊ณต์œ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๋Š” SQLDelight, ์˜์กด์„ฑ ์ฃผ์ž…์—๋Š” Koin์„ ๋ชฉํ‘œ๋กœ ์‚ผ์Šต๋‹ˆ๋‹ค. iOS ์ƒํ˜ธ์šด์šฉ์„ ์œ„ํ•ด์„œ๋Š” SKIE๋ฅผ ์ผ์ฐ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. Swift ๊ณ„์ธต์„ ์ž‘์„ฑํ•œ ๋’ค์— ๋‚˜์ค‘์— ๋ผ์›Œ ๋„ฃ๋Š” ๊ฒƒ์€ ํ›จ์”ฌ ๋” ๊ณ ํ†ต์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

2026๋…„์˜ Kotlin Multiplatform์€ ๋„ค์ดํ‹ฐ๋ธŒ UI๋‚˜ ์„ฑ๋Šฅ์„ ํฌ๊ธฐํ•˜์ง€ ์•Š์œผ๋ฉด์„œ Android์™€ iOS์— ๊ฑธ์นœ ์ค‘๋ณต ๋กœ์ง์„ ์ค„์ด๋Š” ์‹ค์šฉ์ ์ธ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

  • ๋‹จ์ผ ์ง„์‹ค ๊ณต๊ธ‰์›์„ ๊ฐ€์ง„ ๋กœ์ง(๋ชจ๋ธ, ๋„คํŠธ์›Œํ‚น, ๊ฒ€์ฆ)์„ ๊ณต์œ ํ•˜๊ณ , ํ™”๋ฉด๋งˆ๋‹ค ๋„ค์ดํ‹ฐ๋ธŒ UI์™€ Compose UI ์ค‘์—์„œ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.
  • ํ”Œ๋žซํผ API์—๋Š” expect/actual์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ปดํŒŒ์ผ ์‹œ์ ์— ํ•ด๊ฒฐ๋˜์–ด ๋Ÿฐํƒ€์ž„ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.
  • Ktor์™€ kotlinx.serialization์œผ๋กœ ๋„คํŠธ์›Œํ‚น์„ ํ•œ ๋ฒˆ๋งŒ ์ž‘์„ฑํ•˜๋ฉด ์ˆ˜์ •์ด ์–‘์ชฝ ํ”Œ๋žซํผ์— ๋™์‹œ์— ๋ฐ˜์˜๋ฉ๋‹ˆ๋‹ค.
  • ์žฌ์ž‘์„ฑ์— ๋ชฐ๋‘ํ•˜๋Š” ๋Œ€์‹  AAR๊ณผ XCFramework๋ฅผ ํ†ตํ•ด ์ ์ง„์ ์œผ๋กœ ๋„์ž…ํ•ฉ๋‹ˆ๋‹ค.
  • ์ฒ˜์Œ๋ถ€ํ„ฐ SKIE๋ฅผ ์ถ”๊ฐ€ํ•ด Swift๊ฐ€ suspend์™€ Flow๋ฅผ ๋„ค์ดํ‹ฐ๋ธŒ async/await์™€ AsyncSequence๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

์—ฐ์Šต์„ ์‹œ์ž‘ํ•˜์„ธ์š”!

๋ฉด์ ‘ ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ์™€ ๊ธฐ์ˆ  ํ…Œ์ŠคํŠธ๋กœ ์ง€์‹์„ ํ…Œ์ŠคํŠธํ•˜์„ธ์š”.

ํƒœ๊ทธ

#Kotlin Multiplatform
#KMP
#Android
#iOS
#Cross-platform

๊ณต์œ 

๊ด€๋ จ ๊ธฐ์‚ฌ

Android 16 ์ƒˆ๋กœ์šด API, ๋ฐ์Šคํฌํ†ฑ ๋ชจ๋“œ, ๋ฉด์ ‘ ๋Œ€๋น„ ๊ฐ€์ด๋“œ

Android 16 ์™„์ „ ๋ถ„์„: ์ƒˆ๋กœ์šด API, ๋ฐ์Šคํฌํ†ฑ ๋ชจ๋“œ, ๋ฉด์ ‘ ๋Œ€๋น„ ๊ฐ€์ด๋“œ

Android 16(API 36)์˜ ํ•ต์‹ฌ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ์‹ฌ์ธต ๋ถ„์„ํ•ฉ๋‹ˆ๋‹ค. Edge-to-Edge ๊ฐ•์ œ ์ ์šฉ, ๋ฐ์Šคํฌํ†ฑ ๋ชจ๋“œ, ProgressStyle ์•Œ๋ฆผ, Predictive Back ์ œ์Šค์ฒ˜ ๋“ฑ 2026๋…„ ์•ˆ๋“œ๋กœ์ด๋“œ ๊ฐœ๋ฐœ์ž ๋ฉด์ ‘ ํ•„์ˆ˜ ์ฃผ์ œ๋ฅผ ๋‹ค๋ฃน๋‹ˆ๋‹ค.

์•ˆ๋“œ๋กœ์ด๋“œ ๋ฉด์ ‘์„ ์œ„ํ•œ Kotlin Flow vs StateFlow vs SharedFlow ๋น„๊ต

Kotlin Flow vs StateFlow vs SharedFlow: 2026 ์•ˆ๋“œ๋กœ์ด๋“œ ๋ฉด์ ‘ ์งˆ๋ฌธ

2026๋…„ ์•ˆ๋“œ๋กœ์ด๋“œ ๋ฉด์ ‘๊ด€์ด ๋ฌป๋Š” Kotlin Flow vs StateFlow vs SharedFlow ์งˆ๋ฌธ์„ ๋ช…ํ™•ํ•œ ๋‹ต๋ณ€, ๋น„๊ต ํ‘œ, ์‹ค๋ฌด์šฉ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

Android Dependency Injection: Hilt vs Koin comparison guide

Android ์˜์กด์„ฑ ์ฃผ์ž…: Hilt vs Koin ์™„์ „ ๊ฐ€์ด๋“œ ๋ฐ ๋ฉด์ ‘ ์งˆ๋ฌธ 2026

Hilt์™€ Koin์„ ์ฝ”๋“œ ์˜ˆ์ œ, ์„ฑ๋Šฅ ๋ฒค์น˜๋งˆํฌ, ๋ฉด์ ‘ ์งˆ๋ฌธ์œผ๋กœ ์ฒ ์ €ํžˆ ๋น„๊ตํ•ฉ๋‹ˆ๋‹ค. Hilt 2.57, Koin 4.2 ๊ธฐ๋ฐ˜ 2026๋…„ ์ตœ์‹  ๊ฐ€์ด๋“œ.