우테코 8기/Kotlin

expect / actual 키워드 (Kotlin Multiplatform, KMP)

Reddish 2026. 8. 17. 11:50
반응형

expect / actual 키워드는 KMP에서 제공된다. 원래는 KMP를 쓰면서 많이 사용했었는데, 최근들어서 Android ViewModel에서도 코드를 타고 들어가다보면 expect/actual 이 나오기도 한다.

멀티 플랫폼을 지원하면서 안드로이드, ios 모두의 비즈니스로직을 구현하기 위해 kotlin 함수에 붙일 수 있는 키워드이다.

함수, 프로퍼티, 클래스, 인터페이스, 열거형, 어노테이션에 해당 키워드를 적용할 수 있다.

일종의 인터페이스나 추상클래스와 비슷한 역할을 한다. 다만 의도는 다르다. abstract class 의 경우 자신을 상속하는 클래스가 추상체로 포함된 함수, 변수를 구현하도록 하지만, expect는 KMP 내 공통 모듈에서 선언된 선언체가 Android 나 iOS 각각에서 구현되도록 강제하기 위해서 만들어졌다.

즉, 해당하는 함수 또는 클래스를 각 플랫폼에서 동작하는 함수를 만들어야 함을 의미한다.

사용법

commonMain 모듈에 아래와 같이 expect 함수를 만들어보자.

// src/shared/commonMain

expect fun createHttpClient(): HttpClient

HttpClient 타입을 반환하는 createHttpClient 함수를 각 플랫폼 별 모듈에 정의해줘야 함을 의미한다.

해당 함수만 작성하면 에러를 낼 것이다. 이는 해당 함수의 선언이 플랫폼 별 모듈인 androidMain과 iosMain 모듈에 정의되어 있지 않기 때문이다. 따라서 우리는 각 플랫폼 별 모듈에 각각 actual 클래스로 createHttpClient 함수를 만들어야 한다.

// src/shared/iosMain
actual fun createHttpClient(): HttpClient = HttpClient(Darwin) {
		// Darwin은 Apple 에서 개발한 운영체제 코어이다. macOS, iOS의 바탕이 되는 시스템구조.
    configureCommonHttpClient()
}

// src/shared/androidMain
actual fun createHttpClient(): HttpClient = HttpClient(OkHttp) {
		// java 및 안드로이드용 HTTP/HTTP/2 클라이언트 라이브러리
    configureCommonHttpClient()
}

각 모듈에 맞게 구현해주면 된다.

  • expect 사용
    • 해당 선언 내에는 구현을 포함하면 안된다.
    • 모든 expect 선언에는 모든 플랫폼별 소스 세트에 일치하는 actual 선언이 존재해야한다.
  • actual 사용
    • actual 선언에는 해당 expect 선언과 동일한 패키지를 공유해야 한다.
    • 즉, expect가 commonMain/data/HttpClient.kt 에 존재한다면 actual 또한 (platformName)Main/data/HttpClient.kt 파일 내에 존재해야한다.

의존성 주입에 대한 권장 사항

일반적으로 결합도를 낮추기 위해서 DI 프레임워크를 사용하는 경우가 많다. android로 예를 들면 Dagger나 Hilt같은 라이브러리나, Kotlin 이라면 Koin과 같은 라이브러리를 사용할 수 있을 것이다.

공통 코드에서 컴포넌트는 일반 Kotlin 인터페이스를 사용해 자신의 의존성을 표현할 수 있다. 그리고 DI 프레임워크를 설정해 각 모듈에서 플랫폼 별로 구현을 주입하도록 할수 있다.

// commonMain
import org.koin.dsl.module

interface Platform

expect val platformModule: Module
// androidMain
class AndroidPlatform: Platform

actual val platformModule: Module = module {
		single<Platform> {
				AndroidPlatform()
		}
}
// iosMain
class IOSPlatform: Platform

actual val platformModule: Module = module {
		single<Platform> {
				IOSPlatform()
		}
}

Koin DSL은 주입할 컴포넌트를 정의하는 모듈을 생성한다. expect 키워드로 공통된 모듈을 생성하고 actual 키워드를 통해서 플랫폼 특화 코드를 구현할 수 있다.

DI 프레임워크를 사용한다면, 모든 의존성을 이 프레임워크를 통해 주입한다. 플랫폼 의존성을 처리할 때도 동일한 논리가 적용된다. expect, actual 함수를 수동으로 사용하는 것보다 이미 프로젝트에 DI가 포함된 경우 지속한 것을 권장한다. 그러면 의존성을 주입하는 두 가지 다른 방식이 섞이는 것을 피할 수 있다.

또, 항상 Kotlin으로 공통 인터페이스를 구현할 필요가 없다. 즉, 각각의 네이티브 코드에서 DI를 제공할 수 있다는 말이다.

반응형

'우테코 8기 > Kotlin' 카테고리의 다른 글

Flow, StateFlow, SharedFlow  (0) 2026.10.04
Kotlin Annotation Retention  (0) 2026.09.20
Kotlin coroutine - suspend 함수  (0) 2026.09.05
가변성을 제한하라  (0) 2026.07.19
Annotation과 Annotation Target  (2) 2026.06.03