TechFeedTechFeed
Programming Languages

Kotlin Multiplatform 실전 가이드

KMP 구조, Android/iOS/Web 코드 공유, Compose Multiplatform 실전 적용 사례.

한 줄 요약: Kotlin Multiplatform(KMP)은 비즈니스 로직을 한 번 작성하고 Android, iOS, 웹, 데스크톱에서 공유하는 실용적인 접근이다. UI는 각 플랫폼 네이티브를 그대로 쓰거나 Compose Multiplatform으로 통합할 수 있으며, 2024년 1.0 안정화로 프로덕션 도입이 현실적이다.

이 글이 필요한 사람
  • Android 앱과 iOS 앱에서 중복 비즈니스 로직을 공유하고 싶은 팀
  • React Native나 Flutter 대신 네이티브 성능을 유지하면서 코드 공유를 원하는 경우
  • 기존 Android Kotlin 코드베이스를 iOS에 점진적으로 확장하려는 팀
  • KMP와 Compose Multiplatform의 범위 차이를 파악하고 싶은 경우

※ Kotlin Multiplatform 1.0 안정 릴리스(2023년 11월) 및 Compose Multiplatform 1.6(2024년) 기준. 공식 문서: jetbrains.com/kotlin-multiplatform

KMP 구조 — 공유 레이어와 플랫폼 레이어

KMP의 핵심 아이디어는 공유할 수 있는 것만 공유하고, 플랫폼 특화 코드는 각 플랫폼에 남긴다는 원칙이다. React Native나 Flutter처럼 UI까지 완전 통합하는 방식과 달리, KMP는 비즈니스 로직 공유에 집중하고 UI는 각 플랫폼의 네이티브 방식을 유지할 수 있다.

KMP 모듈 구조:

  • commonMain — 모든 플랫폼에서 실행되는 순수 Kotlin 코드. 비즈니스 로직, 데이터 모델, API 클라이언트, Repository 패턴.
  • androidMain — Android 전용 구현. Android SDK, Room, Jetpack 라이브러리.
  • iosMain — iOS 전용 구현. Foundation, UIKit 관련 코드, Swift Interop.
  • jsMain — 웹(Kotlin/JS) 전용 구현.
  • desktopMain — JVM 기반 데스크톱(macOS, Windows, Linux).

expect/actual 메커니즘: 플랫폼마다 다른 구현이 필요한 경우 expect로 인터페이스를 선언하고, 각 플랫폼 소스셋에서 actual로 구현한다. 예: 플랫폼별 로컬 스토리지, 암호화, 날짜 포맷.

KMP 프로젝트 구조 및 expect/actual 예시
// build.gradle.kts — KMP 멀티플랫폼 설정 kotlin { androidTarget() iosX64() iosArm64() iosSimulatorArm64() sourceSets { commonMain.dependencies { implementation("io.ktor:ktor-client-core:2.3.7") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3") } androidMain.dependencies { implementation("io.ktor:ktor-client-okhttp:2.3.7") } iosMain.dependencies { implementation("io.ktor:ktor-client-darwin:2.3.7") } } } // commonMain/Platform.kt — expect 선언 expect fun getPlatformName(): String expect class PlatformLogger() { fun log(message: String) } // androidMain/Platform.android.kt — actual 구현 actual fun getPlatformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}" actual class PlatformLogger actual constructor() { actual fun log(message: String) = android.util.Log.d("KMP", message) } // iosMain/Platform.ios.kt — actual 구현 actual fun getPlatformName(): String = UIDevice.currentDevice.systemName() actual class PlatformLogger actual constructor() { actual fun log(message: String) = NSLog("%@", message) }

Shared Module 실전 예시 — Repository 패턴

실무에서 가장 많이 공유하는 코드는 네트워크 요청, 데이터 모델, 비즈니스 규칙이다. 아래는 Ktor + Kotlin Serialization을 사용한 공통 API 클라이언트와 Repository 구현이다.

commonMain — 공유 Repository 및 API 클라이언트
// commonMain/data/UserRepository.kt import io.ktor.client.* import io.ktor.client.call.* import io.ktor.client.request.* import kotlinx.serialization.Serializable @Serializable data class User( val id: Int, val name: String, val email: String ) class UserRepository(private val client: HttpClient) { suspend fun getUser(id: Int): Result<User> { return runCatching { client.get("https://api.example.com/users/$id").body<User>() } } suspend fun getUsers(): Result<List<User>> { return runCatching { client.get("https://api.example.com/users").body<List<User>>() } } } // commonMain/di/HttpClientFactory.kt import io.ktor.client.* import io.ktor.client.plugins.contentnegotiation.* import io.ktor.serialization.kotlinx.json.* fun createHttpClient(): HttpClient = HttpClient { install(ContentNegotiation) { json() } }

위 코드는 commonMain에 작성되며 Android와 iOS 모두에서 동일하게 동작한다. Android에서는 OkHttp 엔진, iOS에서는 Darwin(NSURLSession) 엔진이 자동으로 선택된다. 비즈니스 로직은 완전히 공유되면서 플랫폼별 네트워크 구현은 KMP가 처리한다.

Compose Multiplatform — UI도 공유하려면

Compose Multiplatform(CMP)은 KMP 위에 UI 공유 레이어를 추가한다. Jetpack Compose 문법으로 작성한 UI를 Android, iOS, 웹(Wasm), 데스크톱에서 공유한다.

KMP vs Compose Multiplatform 차이:

항목KMP (비즈니스 로직만)Compose Multiplatform (UI 포함)
공유 범위비즈니스 로직, 네트워킹, 데이터UI + 비즈니스 로직 전체
iOS UISwiftUI / UIKit (네이티브)Compose로 렌더링 (Skia 엔진)
안정성Stable (1.0)iOS Alpha → Beta 진행 중
플랫폼 접근성각 플랫폼 접근성 그대로Skia 렌더링으로 접근성 제한
추천 케이스플랫폼 룩앤필 유지 필요내부 도구, B2B, 디자인 통일 우선

소비자 대면 앱이라면 각 플랫폼의 네이티브 룩앤필을 유지하는 것이 사용자 경험상 유리하다. 내부 관리 도구, 대시보드, B2B 앱에서는 Compose Multiplatform으로 UI까지 완전히 공유하는 것도 실용적이다.

iOS 통합 — Swift에서 KMP 모듈 사용하기

KMP의 iOS 통합은 Kotlin 코드를 Objective-C 프레임워크(XCFramework)로 빌드해 Swift에서 호출하는 방식이다. Kotlin coroutines는 Swift의 async/await로 변환되며, Kotlin의 sealed class는 Swift에서 enum-like 패턴으로 사용할 수 있다.

iOS — XCFramework 빌드 및 Swift에서 KMP 호출
# XCFramework 빌드 (Gradle 태스크) ./gradlew assembleXCFramework # → build/XCFrameworks/release/shared.xcframework 생성 # Xcode에서 shared.xcframework를 Frameworks에 추가 후 // Swift에서 KMP 코드 호출 import shared // KMP 모듈 import class UserViewModel: ObservableObject { @Published var user: User? @Published var errorMessage: String? private let repository = UserRepository(client: createHttpClient()) func loadUser(id: Int) { // Kotlin suspend 함수 → Swift async/await 변환 Task { do { // KotlinInt로 변환 필요 let result = try await repository.getUser(id: Int32(id)) await MainActor.run { self.user = result } } catch { await MainActor.run { self.errorMessage = error.localizedDescription } } } } }
Swift Concurrency 주의: Kotlin coroutines와 Swift async/await의 변환은 SKIE(Swift/Kotlin Interface Enhancer) 라이브러리를 함께 사용하면 훨씬 자연스러워진다. 기본 KMP-Swift 브릿지만으로는 콜백 방식으로 노출되는 경우가 있다. SKIE는 Touchlab에서 오픈소스로 제공한다.

실무 적용 사례와 도입 판단 기준

실제 도입 사례:

  • Netflix: 모바일 앱의 일부 비즈니스 로직 공유에 KMP 사용
  • Touchlab: 자사 앱과 클라이언트 프로젝트에서 KMP 프로덕션 적용, SKIE 오픈소스 개발
  • Philips: 의료기기 데이터 처리 로직 Android/iOS 공유
  • Square: SDK 레이어에 KMP 도입

도입하기 좋은 상황:

  • Android 팀이 Kotlin에 이미 능숙하고, iOS 네이티브도 유지하고 싶은 경우
  • API 클라이언트, 캐싱, 비즈니스 규칙이 두 플랫폼에서 중복 구현된 경우
  • 새 기능을 한 팀이 두 플랫폼에 동시 출시해야 하는 경우

주의가 필요한 상황:

  • iOS 팀이 Swift 외 코드에 익숙하지 않아 KMP 디버깅이 어려운 경우
  • 서드파티 라이브러리를 많이 쓰는데 KMP 버전이 없는 경우 (expect/actual로 래핑 필요)
  • iOS 앱의 비중이 매우 낮아 공유 이득이 마이그레이션 비용보다 작은 경우
점진적 도입 전략: 기존 Android 앱에 KMP를 전면 도입하는 것보다, 신규 피처 하나를 공유 모듈로 작성하고 Android에서 먼저 검증한 뒤 iOS에 붙이는 순서가 현실적이다. 네트워킹 레이어(Ktor + Repository)부터 시작하면 리스크가 낮다.
KotlinKMP멀티플랫폼Compose모바일

관련 포스트

웹 개발자를 위한 Rust 입문2026-02-28Go로 마이크로서비스 구축하기2026-03-01Python 3.13 새 기능 총정리2026-03-08JavaScript ES2026 새 기능 정리2026-03-10