앱 하나 만들려면 iOS 따로, Android 따로 개발해야 하는 시대는 서서히 저물고 있습니다. 특히 Kotlin Multiplatform(KMP)은 네이티브 성능을 포기하지 않으면서도 비즈니스 로직을 공유할 수 있다는 점에서 최근 모바일 개발자들 사이에서 뜨거운 관심을 받고 있죠. 저도 실제 프로젝트에 KMP를 도입해보면서 느낀 점들이 많은데요, 오늘은 그 경험을 바탕으로 솔직하게 이야기해보겠습니다.
1. Kotlin Multiplatform이란 무엇인가
1-1. KMP의 핵심 개념
Kotlin Multiplatform은 JetBrains에서 만든 크로스플랫폼 기술로, 하나의 Kotlin 코드베이스에서 Android, iOS, 웹, 데스크톱 등 여러 플랫폼을 동시에 타깃할 수 있게 해줍니다. 여기서 중요한 포인트는 "UI까지 공유하자"가 아니라 "비즈니스 로직을 공유하자"라는 철학입니다. UI는 각 플랫폼 네이티브로 구현하고, 데이터 처리·네트워크 통신·상태 관리 같은 핵심 로직만 공통 모듈에 작성하는 방식이죠.
Flutter나 React Native가 UI까지 통째로 공유하는 접근이라면, KMP는 각 플랫폼의 네이티브 UI 경험을 100% 살리면서 중복 코드만 줄이는 전략입니다. 그래서 기존 네이티브 앱에 점진적으로 도입하기가 훨씬 수월합니다.
1-2. KMP vs Compose Multiplatform 차이
많이들 헷갈리시는 부분인데, KMP와 Compose Multiplatform은 다른 개념입니다. KMP는 로직 공유를 위한 프레임워크이고, Compose Multiplatform은 그 위에 UI까지 공유할 수 있도록 확장한 것입니다. Compose Multiplatform을 사용하면 Jetpack Compose 스타일의 선언형 UI를 iOS에서도 쓸 수 있게 되는 거죠. 다만 Compose Multiplatform의 iOS 지원은 아직 성숙도 측면에서 프로덕션에 바로 적용하기엔 고민이 필요한 단계입니다. 저는 개인적으로 로직만 공유하는 순수 KMP 방식을 먼저 추천드립니다.
1-3. 실제 도입 사례들
Netflix, Cash App, Philips, McDonald's 등 글로벌 기업들이 이미 Kotlin Multiplatform을 프로덕션에 활용하고 있습니다. 특히 Cash App은 네트워킹 레이어와 비즈니스 로직 전체를 KMP로 공유하면서 두 플랫폼 간 버그 불일치를 크게 줄였다고 밝힌 바 있습니다. 국내에서도 카카오, 라인 등에서 부분적으로 도입을 검토하거나 적용 중인 것으로 알려져 있습니다.
2. 프로젝트 구조와 시작하기
2-1. 프로젝트 기본 구조 이해하기
KMP 프로젝트는 크게 세 가지 소스 셋으로 나뉩니다. commonMain에는 플랫폼에 독립적인 공통 코드를, androidMain에는 Android 전용 코드를, iosMain에는 iOS 전용 코드를 작성합니다. 플랫폼별로 다르게 동작해야 하는 부분은 expect/actual 메커니즘을 통해 선언과 구현을 분리합니다.
예를 들어 commonMain에서 expect fun getPlatformName(): String으로 선언하면, androidMain에서는 actual fun getPlatformName() = "Android", iosMain에서는 actual fun getPlatformName() = "iOS"로 각각 구현하는 식이죠. 이 구조가 처음엔 낯설 수 있지만, 익숙해지면 꽤 직관적입니다.
2-2. 개발 환경 설정
Android Studio에서 KMP 플러그인을 설치하면 바로 시작할 수 있습니다. JetBrains에서 제공하는 KMP Wizard(kmp.jetbrains.com)를 이용하면 웹에서 프로젝트 템플릿을 생성할 수도 있어서 초기 설정이 한결 편해졌습니다. iOS 빌드를 위해서는 macOS 환경과 Xcode가 필요한데, 이 부분이 Windows나 Linux 사용자에게는 진입 장벽이 될 수 있습니다.
Gradle 설정이 다소 복잡한 편이라 처음 셋업할 때 시간이 좀 걸립니다. 하지만 한번 잡아놓으면 이후에는 크게 손댈 일이 없으니, 초기 셋업에 시간을 충분히 투자하시길 권합니다.
2-3. 핵심 라이브러리 생태계
KMP 생태계는 빠르게 성장하고 있습니다. 주요 라이브러리를 정리하면 다음과 같습니다:
- Ktor — 네트워크 통신 (HTTP 클라이언트)
- SQLDelight — 로컬 데이터베이스 (SQLite 기반)
- Koin / Kotlin Inject — 의존성 주입
- Kotlinx.serialization — JSON 직렬화/역직렬화
- Kotlinx.coroutines — 비동기 처리
- Multiplatform Settings — SharedPreferences/UserDefaults 공유
이 정도면 웬만한 앱의 비즈니스 로직은 충분히 공통 모듈로 작성할 수 있습니다. 다만 아직 서드파티 라이브러리 중 KMP를 지원하지 않는 것들도 있으므로, 프로젝트 시작 전에 필요한 라이브러리의 멀티플랫폼 지원 여부를 꼭 확인하세요.
3. 장단점 솔직 비교
3-1. 다른 크로스플랫폼 프레임워크와 비교
| 항목 | Kotlin Multiplatform | Flutter | React Native |
|---|---|---|---|
| UI 공유 | 네이티브 UI 유지 (선택적 공유) | 완전 공유 (자체 렌더링) | 완전 공유 (브릿지 기반) |
| 네이티브 성능 | 최상 (네이티브 그 자체) | 우수 | 보통 |
| 기존 앱 통합 | 매우 쉬움 (점진적 도입) | 어려움 (모듈 방식 가능) | 보통 |
| 학습 곡선 | Android 개발자: 낮음 / iOS: 높음 | Dart 학습 필요 | JS/React 경험 시 낮음 |
| 코드 공유 범위 | 로직 중심 (50~70%) | 거의 전체 (80~95%) | 거의 전체 (70~90%) |
| 생태계 성숙도 | 성장 중 | 성숙 | 성숙 |
| iOS 개발자 친화성 | 보통 (Swift 연동 필요) | 낮음 | 낮음 |
| 빌드 속도 | 보통 (Gradle 기반) | 빠름 | 빠름 |
3-2. KMP만의 강점과 약점
강점:
- 기존 네이티브 프로젝트에 모듈 하나 추가하는 식으로 점진적 도입이 가능합니다. 전면 재작성이 필요 없다는 건 실무에서 정말 큰 장점이에요.
- 각 플랫폼의 네이티브 UI를 그대로 사용하므로, 플랫폼 가이드라인을 완벽히 준수할 수 있습니다.
- Android 개발자라면 이미 익숙한 Kotlin 언어를 그대로 쓰기 때문에 추가 학습 부담이 적습니다.
- Google이 공식적으로 KMP를 Android 개발 권장 기술로 지정하면서 장기적 지원에 대한 신뢰도가 높아졌습니다.
약점:
- iOS 빌드를 위해 macOS가 반드시 필요합니다. CI/CD 파이프라인 구성 시에도 Mac 러너가 필요하죠.
- Gradle 빌드 설정이 복잡하고, 빌드 시간이 다소 길 수 있습니다.
- iOS 개발자 입장에서는 Kotlin이라는 새로운 언어를 배워야 하는 부담이 있습니다.
- 디버깅 시 iOS 쪽 스택 트레이스가 읽기 어려운 경우가 종종 있습니다.
4. 실전 사용 팁
4-1. 도입 전략: 작게 시작하세요
팁 1: 네트워킹 레이어부터 시작하세요. 처음부터 모든 로직을 공유하려고 하면 부담이 큽니다. 저의 경험상 API 통신 모듈(데이터 모델 + API 클라이언트)을 가장 먼저 공유하는 게 효과가 좋았습니다. 양쪽 플랫폼에서 동일한 API 응답 모델을 사용하게 되면 그것만으로도 버그가 눈에 띄게 줄어듭니다. Ktor + Kotlinx.serialization 조합이면 깔끔하게 구현할 수 있어요.
팁 2: expect/actual은 최소한으로 사용하세요. 플랫폼별 분기가 많아질수록 코드 관리가 복잡해집니다. 가능하면 인터페이스와 의존성 주입 패턴으로 플랫폼 차이를 추상화하고, expect/actual은 정말 필요한 경우에만 쓰는 게 좋습니다. 예를 들어 플랫폼별 UUID 생성이나 파일 경로 같은 아주 기본적인 부분에만 사용하세요.
4-2. 개발 생산성을 높이는 방법
팁 3: iOS 팀과의 소통에 투자하세요. KMP 도입에서 기술적 난이도보다 더 어려운 건 팀 간 협업입니다. iOS 개발자가 공유 모듈의 코드를 읽고 수정할 수 있어야 장기적으로 유지보수가 가능합니다. 공유 모듈의 API를 Swift에서 자연스럽게 사용할 수 있도록 설계하고, iOS 팀이 Kotlin 기본 문법 정도는 이해할 수 있도록 내부 세미나를 진행하는 것을 권합니다.
팁 4: SKIE 라이브러리를 적극 활용하세요. Touchlab에서 만든 SKIE는 KMP에서 생성되는 Objective-C 인터페이스를 Swift 친화적으로 변환해줍니다. sealed class가 Swift의 enum처럼 동작하게 해주고, suspend 함수를 Swift의 async/await로 자연스럽게 사용할 수 있게 해줍니다. iOS 쪽 개발 경험이 확실히 좋아지므로 꼭 도입을 검토해보세요.
4-3. 흔한 실수와 해결법
팁 5: 메모리 관리에 주의하세요. 예전에는 Kotlin/Native의 메모리 모델이 큰 골칫거리였지만, 새로운 메모리 관리자가 도입되면서 많이 개선되었습니다. 그래도 iOS 쪽에서 코루틴을 사용할 때 메모리 누수가 발생하지 않도록 적절한 스코프 관리는 여전히 중요합니다. 공유 모듈에서 CoroutineScope를 노출할 때는 반드시 취소 메커니즘도 함께 제공하세요.
또 하나, Gradle 버전과 Kotlin 버전, 각종 플러그인 버전의 호환성 문제로 빌드가 깨지는 경우가 잦습니다. 버전 업그레이드는 한 번에 하나씩, 그리고 반드시 양쪽 플랫폼 빌드를 모두 확인한 뒤 커밋하는 습관을 들이세요.
4-4. 빌드와 CI — 도입 전에 확인할 것
KMP를 팀에 들일 때 코드보다 먼저 걸리는 것이 빌드 환경입니다. iOS 산출물을 만들려면 Xcode가 필요하고, Xcode는 macOS에서만 돕니다. 즉 안드로이드만 만들던 팀이라도 맥 장비나 맥 러너가 한 대는 있어야 공유 모듈이 iOS에서 실제로 도는지 확인할 수 있습니다. 이걸 도입 후에 알게 되면 "공유했는데 검증은 못 하는" 상태로 몇 주를 보내게 됩니다.
빌드 시간도 미리 감안하세요. 공유 모듈을 고치면 안드로이드 컴파일과 별개로 iOS용 네이티브 바이너리를 다시 링크해야 해서, 순수 안드로이드 프로젝트보다 느려지는 구간이 생깁니다. Gradle 빌드 캐시와 설정 캐시를 켜고, CI에서는 ~/.gradle과 코틀린 네이티브 의존성 캐시를 남겨 두는 것만으로 체감이 크게 달라집니다.
마지막으로 공유 모듈은 반드시 두 플랫폼 모두에서 CI를 돌리세요. 한쪽만 검사하면 expect/actual의 한쪽 구현이 빠진 채 병합되는 사고가 납니다. 이건 컴파일 단계에서 잡히는 종류라, 파이프라인에 한 줄 추가하는 비용으로 막을 수 있습니다.
5. 마무리: 누구에게 추천하는가
5-1. KMP가 잘 맞는 팀
- 이미 Kotlin으로 Android 앱을 운영 중인 팀 — 가장 자연스러운 전환이 가능합니다.
- 네이티브 UX를 포기할 수 없는 서비스 — 금융, 헬스케어 등 각 플랫폼 가이드라인 준수가 중요한 도메인.
- 점진적 마이그레이션을 원하는 팀 — 기존 앱을 전면 재작성할 여력은 없지만, 코드 중복은 줄이고 싶은 경우.
- 비즈니스 로직의 정합성이 중요한 서비스 — 양쪽 플랫폼에서 동일한 계산 결과, 동일한 유효성 검사가 필요한 경우.
5-2. 다른 선택지가 나을 수 있는 경우
- 빠른 MVP 개발이 목표라면 — Flutter가 UI까지 한 번에 공유할 수 있어서 초기 개발 속도가 더 빠릅니다.
- 웹 프론트엔드 팀이 앱도 담당한다면 — React Native가 기존 역량을 활용하기에 유리합니다.
- iOS만 지원하는 서비스라면 — 굳이 KMP를 도입할 이유가 없겠죠.
Kotlin Multiplatform은 "모든 것을 하나로 통일하자"는 접근이 아니라, "공유할 수 있는 건 공유하고 각자의 강점은 살리자"는 현실적인 철학을 가진 기술입니다. 2024년 Stable 출시 이후로 생태계가 빠르게 안정되고 있고, Google의 공식 지원까지 더해지면서 앞으로의 성장이 더 기대됩니다. 만약 여러분의 팀이 Android와 iOS를 동시에 개발하면서 코드 품질과 네이티브 경험 모두를 중시한다면, KMP는 분명 한번 진지하게 검토해볼 만한 선택지입니다.