Theme:

"안드로이드, iOS, 웹, 서버에서 같은 비즈니스 로직을 각각 다시 작성하고 있다면, 하나의 코드베이스로 모든 플랫폼을 지원할 수는 없을까요?"

Kotlin Multiplatform(KMP)은 하나의 코틀린 코드베이스로 JVM, JavaScript, iOS(Native), WebAssembly 등 여러 플랫폼을 타겟팅하는 기술입니다. "한 번 작성하면 어디서든 실행"(Write Once, Run Anywhere)이 아니라 "핵심 로직을 공유하고, 플랫폼별로 필요한 부분만 따로 구현"하는 접근입니다.

KMP의 핵심 아이디어

KMP는 코드를 세 영역으로 나눕니다.

PLAINTEXT
┌─────────────────────────────────┐
│        commonMain               │ ← 플랫폼 무관한 순수 코틀린
│  (비즈니스 로직, 데이터 모델)    │
├─────────┬───────────┬───────────┤
│ jvmMain │  iosMain  │  jsMain   │ ← 플랫폼별 구현
│ (JVM)   │  (iOS)    │  (JS)     │
└─────────┴───────────┴───────────┘
  • commonMain: 모든 플랫폼에서 공유하는 코드 (순수 코틀린)
  • 플랫폼 소스셋: 각 플랫폼에 특화된 구현 (파일 시스템, 네트워크, UI 등)

프로젝트 구조

기본적인 KMP 프로젝트 구조입니다.

PLAINTEXT
my-kmp-project/
├── build.gradle.kts
├── shared/
│   ├── build.gradle.kts
│   └── src/
│       ├── commonMain/kotlin/    ← 공유 코드
│       ├── commonTest/kotlin/    ← 공유 테스트
│       ├── jvmMain/kotlin/       ← JVM 전용
│       ├── iosMain/kotlin/       ← iOS 전용
│       ├── jsMain/kotlin/        ← JS 전용
│       └── ...
├── androidApp/                   ← Android 앱
├── iosApp/                       ← iOS 앱 (Xcode 프로젝트)
└── webApp/                       ← 웹 앱

build.gradle.kts 설정

KOTLIN
plugins {
    kotlin("multiplatform") version "1.9.0"
}

kotlin {
    // 타겟 플랫폼 선언
    jvm()
    iosArm64()
    iosSimulatorArm64()
    js(IR) {
        browser()
        nodejs()
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")
                implementation("io.ktor:ktor-client-core:2.3.0")
            }
        }
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }
        val jvmMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-okhttp:2.3.0")
            }
        }
        val iosMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-darwin:2.3.0")
            }
        }
        val jsMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-js:2.3.0")
            }
        }
    }
}

expect/actual — 플랫폼별 구현 연결

공통 코드에서 플랫폼별 구현이 필요한 부분을 expect로 선언하고, 각 플랫폼에서 actual로 구현합니다.

함수

KOTLIN
// commonMain — 선언만
expect fun platformName(): String

// jvmMain
actual fun platformName(): String = "JVM ${System.getProperty("java.version")}"

// iosMain
actual fun platformName(): String = "iOS ${UIDevice.currentDevice.systemVersion}"

// jsMain
actual fun platformName(): String = "JavaScript"

클래스

KOTLIN
// commonMain
expect class UUID {
    fun toHexString(): String
}

// jvmMain
actual class UUID(private val uuid: java.util.UUID) {
    actual fun toHexString(): String = uuid.toString()

    companion object {
        fun randomUUID() = UUID(java.util.UUID.randomUUID())
    }
}

// iosMain
actual class UUID(private val uuid: platform.Foundation.NSUUID) {
    actual fun toHexString(): String = uuid.UUIDString()

    companion object {
        fun randomUUID() = UUID(platform.Foundation.NSUUID())
    }
}

typealias를 활용한 간단한 매핑

플랫폼 타입이 이미 같은 인터페이스를 제공하는 경우 typealias로 연결할 수 있습니다.

KOTLIN
// commonMain
expect class AtomicInt(value: Int) {
    fun get(): Int
    fun set(value: Int)
    fun incrementAndGet(): Int
}

// jvmMain — 기존 클래스를 그대로 사용
actual typealias AtomicInt = java.util.concurrent.atomic.AtomicInteger

공유 가능한 코드 범위

KMP에서 무엇을 공유할 수 있는지 정리합니다.

공유하기 좋은 것

  • 데이터 모델: data class, 직렬화/역직렬화
  • 비즈니스 로직: 유효성 검증, 계산, 상태 관리
  • 네트워크 클라이언트: Ktor를 사용한 API 호출
  • 로컬 저장소: SQLDelight를 사용한 DB 접근
  • 유틸리티: 날짜 처리, 문자열 변환 등

플랫폼별로 구현해야 하는 것

  • UI: 각 플랫폼의 네이티브 UI 또는 Compose Multiplatform
  • 플랫폼 API: 카메라, GPS, 푸시 알림 등
  • 파일 시스템: 플랫폼별 경로와 접근 방식
  • 스레딩: 플랫폼별 동시성 모델 (코루틴으로 추상화 가능)

실전 예제 — API 클라이언트 공유

KOTLIN
// commonMain
class ApiClient(private val httpClient: HttpClient) {

    suspend fun getUser(id: Long): UserDto {
        return httpClient.get("https://api.example.com/users/$id").body()
    }

    suspend fun createUser(request: CreateUserRequest): UserDto {
        return httpClient.post("https://api.example.com/users") {
            contentType(ContentType.Application.Json)
            setBody(request)
        }.body()
    }
}

@Serializable
data class UserDto(
    val id: Long,
    val name: String,
    val email: String
)

@Serializable
data class CreateUserRequest(
    val name: String,
    val email: String
)
KOTLIN
// jvmMain — OkHttp 엔진
fun createApiClient(): ApiClient {
    val httpClient = HttpClient(OkHttp) {
        install(ContentNegotiation) { json() }
    }
    return ApiClient(httpClient)
}

// iosMain — Darwin 엔진
fun createApiClient(): ApiClient {
    val httpClient = HttpClient(Darwin) {
        install(ContentNegotiation) { json() }
    }
    return ApiClient(httpClient)
}

비즈니스 로직과 데이터 모델은 commonMain에 한 번만 작성하고, HTTP 엔진만 플랫폼별로 교체합니다.

Compose Multiplatform

JetBrains가 Google의 Jetpack Compose를 기반으로 멀티플랫폼 UI 프레임워크를 만들었습니다.

KOTLIN
// commonMain에서 UI 작성
@Composable
fun App() {
    MaterialTheme {
        var count by remember { mutableStateOf(0) }

        Column(
            modifier = Modifier.fillMaxSize(),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Text("클릭 횟수: $count", style = MaterialTheme.typography.headlineMedium)
            Spacer(modifier = Modifier.height(16.dp))
            Button(onClick = { count++ }) {
                Text("클릭")
            }
        }
    }
}

이 코드가 Android, iOS, Desktop(JVM), Web에서 모두 동작합니다.

지원 플랫폼별 상태 (2025년 기준)

플랫폼상태비고
AndroidStableJetpack Compose 그대로
iOSBetaUIKit 위에 렌더링
Desktop (JVM)StableSwing/AWT 위에 렌더링
Web (Wasm)AlphaWebAssembly 기반

KMP 도입 시 고려할 점

장점

  • 비즈니스 로직을 한 번만 작성하고 테스트합니다
  • 플랫폼 간 데이터 모델이 항상 동기화됩니다
  • 코틀린 생태계(코루틴, 직렬화, Ktor 등)를 그대로 활용합니다

주의할 점

  • iOS 팀이 코틀린에 익숙하지 않으면 협업 비용이 발생합니다
  • Kotlin/Native의 메모리 모델과 스레딩이 JVM과 다릅니다
  • 빌드 시간이 단일 플랫폼 프로젝트보다 길 수 있습니다
  • 일부 라이브러리가 KMP를 지원하지 않을 수 있습니다

KMP 지원 주요 라이브러리

카테고리라이브러리
네트워크Ktor
직렬화kotlinx.serialization
비동기kotlinx.coroutines
데이터베이스SQLDelight
DIKoin, Kodein
날짜/시간kotlinx-datetime
이미지 로딩Coil (Compose Multiplatform)

정리

  • KMP는 "모든 것을 공유"가 아니라 "공유할 수 있는 것을 공유"하는 실용적 접근입니다
  • expect/actual로 공통 인터페이스와 플랫폼별 구현을 연결합니다
  • commonMain에 비즈니스 로직, 데이터 모델, API 클라이언트를 작성하고, 플랫폼별 소스셋에서 UI와 네이티브 API를 구현합니다
  • Compose Multiplatform으로 UI까지 공유할 수 있으며, Android와 Desktop은 Stable, iOS는 Beta 상태입니다
  • Ktor, kotlinx.serialization, SQLDelight 등 KMP 지원 라이브러리 생태계가 점점 풍부해지고 있습니다
댓글 로딩 중...