Skip to content

플랫폼별 API 사용

이 문서에서는 멀티플랫폼 애플리케이션 및 라이브러리를 개발할 때 플랫폼별 API를 사용하는 방법을 배웁니다.

Kotlin 멀티플랫폼 라이브러리

플랫폼별 API를 사용하는 코드를 작성하기 전에, 대신 멀티플랫폼 라이브러리를 사용할 수 있는지 확인하세요. 이러한 종류의 라이브러리는 다양한 플랫폼에 대해 다른 구현을 가지는 공통 Kotlin API를 제공합니다.

네트워킹, 로깅, 분석을 구현하는 데, 그리고 장치 기능 등에 접근하는 데 사용할 수 있는 많은 라이브러리가 이미 있습니다. 자세한 내용은 이 엄선된 목록을 참조하세요.

예상(expect) 및 실제(actual) 함수와 프로퍼티

Kotlin은 공통 로직을 개발하는 동안 플랫폼별 API에 접근하기 위한 언어 메커니즘을 제공합니다. 바로 예상(expect) 및 실제(actual) 선언입니다.

이 메커니즘을 사용하면 멀티플랫폼 모듈의 공통 소스 세트가 예상 선언을 정의하고, 모든 플랫폼 소스 세트는 예상 선언에 해당하는 실제 선언을 제공해야 합니다. 컴파일러는 공통 소스 세트에서 expect 키워드로 표시된 모든 선언이 모든 대상 플랫폼 소스 세트에서 actual 키워드로 표시된 해당 선언을 가지도록 보장합니다.

이는 함수, 클래스, 인터페이스, 열거형, 프로퍼티, 어노테이션과 같은 대부분의 Kotlin 선언에서 작동합니다. 이 섹션에서는 예상 및 실제 함수와 프로퍼티를 사용하는 데 중점을 둡니다.

예상 및 실제 함수와 프로퍼티 사용

이 예시에서는 공통 소스 세트에 예상 platform() 함수를 정의하고 플랫폼 소스 세트에서 실제 구현을 제공합니다. 특정 플랫폼용 코드를 생성하는 동안 Kotlin 컴파일러는 예상 및 실제 선언을 병합합니다. 컴파일러는 하나의 platform() 함수를 실제 구현과 함께 생성합니다. 예상 및 실제 선언은 동일한 패키지에 정의되어야 하며, 결과 플랫폼 코드에서 _하나의 선언_으로 병합되어야 합니다. 생성된 플랫폼 코드에서 예상 platform() 함수의 모든 호출은 올바른 실제 구현을 호출할 것입니다.

예시: UUID 생성

Kotlin Multiplatform을 사용하여 iOS 및 Android 애플리케이션을 개발하고 있고 범용 고유 식별자(UUID)를 생성하고자 한다고 가정해 봅시다.

이를 위해 Kotlin Multiplatform 모듈의 공통 소스 세트에 expect 키워드와 함께 예상 함수 randomUUID()를 선언합니다. 구현 코드를 포함하지 마십시오.

kotlin
// 공통 소스 세트에서:
expect fun randomUUID(): String

각 플랫폼별 소스 세트(iOS 및 Android)에서 공통 모듈에서 예상된 randomUUID() 함수에 대한 실제 구현을 제공합니다. actual 키워드를 사용하여 이 실제 구현들을 표시하세요.

예상 및 실제 선언으로 UUID 생성

다음 스니펫은 Android 및 iOS용 구현을 보여줍니다. 플랫폼별 코드는 actual 키워드와 함수에 동일한 이름을 사용합니다.

kotlin
// android 소스 세트에서:
import java.util.*

actual fun randomUUID() = UUID.randomUUID().toString()
kotlin
// iOS 소스 세트에서:
import platform.Foundation.NSUUID

actual fun randomUUID(): String = NSUUID().UUIDString()

Android 구현은 Android에서 사용 가능한 API를 사용하는 반면, iOS 구현은 iOS에서 사용 가능한 API를 사용합니다. Kotlin/Native 코드에서 iOS API에 접근할 수 있습니다.

Android용 결과 플랫폼 코드를 생성하는 동안 Kotlin 컴파일러는 예상 및 실제 선언을 자동으로 병합하고 실제 Android별 구현을 가진 단일 randomUUID 함수를 생성합니다. 동일한 프로세스가 iOS에 대해서도 반복됩니다.

단순화를 위해 이 예시와 다음 예시에서는 "common", "ios", "android"와 같은 간소화된 소스 세트 이름을 사용합니다. 일반적으로 이는 commonMain, iosMain, androidMain을 의미하며, 유사한 로직이 테스트 소스 세트인 commonTest, iosTest, androidTest에서도 정의될 수 있습니다.

예상 및 실제 함수와 유사하게, 예상 및 실제 프로퍼티는 다양한 플랫폼에서 다른 값을 사용할 수 있도록 합니다. 예상 및 실제 함수와 프로퍼티는 단순한 경우에 가장 유용합니다.

공통 코드의 인터페이스

플랫폼별 로직이 너무 크고 복잡하다면, 공통 코드에서 이를 나타내는 인터페이스를 정의하고 플랫폼 소스 세트에서 다른 구현을 제공함으로써 코드를 단순화할 수 있습니다.

인터페이스 사용

플랫폼 소스 세트의 구현은 해당 종속성을 사용합니다.

kotlin
// commonMain 소스 세트에서:
interface Platform {
    val name: String
}
kotlin
// androidMain 소스 세트에서:
import android.os.Build

class AndroidPlatform : Platform {
    override val name: String = "Android ${Build.VERSION.SDK_INT}"
}
kotlin
// iosMain 소스 세트에서:
import platform.UIKit.UIDevice

class IOSPlatform : Platform {
    override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

공통 인터페이스가 필요할 때 적절한 플랫폼 구현을 주입하기 위해 다음 옵션 중 하나를 선택할 수 있으며, 각 옵션은 아래에서 더 자세히 설명됩니다.

예상 및 실제 함수

이 인터페이스의 값을 반환하는 예상 함수를 정의하고, 그 서브클래스를 반환하는 실제 함수를 정의합니다.

kotlin
// commonMain 소스 세트에서:
interface Platform

expect fun platform(): Platform
kotlin
// androidMain 소스 세트에서:
class AndroidPlatform : Platform

actual fun platform() = AndroidPlatform()
kotlin
// iosMain 소스 세트에서:
class IOSPlatform : Platform

actual fun platform() = IOSPlatform()

공통 코드에서 platform() 함수를 호출할 때, Platform 타입의 객체와 함께 작동할 수 있습니다. 이 공통 코드를 Android에서 실행하면 platform() 호출은 AndroidPlatform 클래스의 인스턴스를 반환합니다. iOS에서 실행하면 platform()IOSPlatform 클래스의 인스턴스를 반환합니다.

다른 진입점

진입점(entry points)을 제어한다면, 예상 및 실제 선언을 사용하지 않고도 각 플랫폼 아티팩트의 구현을 구성할 수 있습니다. 이를 위해 플랫폼 구현을 공유 Kotlin Multiplatform 모듈에 정의하지만, 플랫폼 모듈에서 인스턴스화(instantiate)합니다.

kotlin
// 공유 Kotlin Multiplatform 모듈
// commonMain 소스 세트에서:
interface Platform

fun application(p: Platform) {
    // application logic
}
kotlin
// androidMain 소스 세트에서:
class AndroidPlatform : Platform
kotlin
// iosMain 소스 세트에서:
class IOSPlatform : Platform
kotlin
// androidApp 플랫폼 모듈에서:
import android.app.Application
import mysharedpackage.*

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        application(AndroidPlatform())
    }
}
Swift
// iosApp 플랫폼 모듈에서 (Swift):
import shared

@main
struct iOSApp : App {
    init() {
        application(IOSPlatform())
    }
}

Android에서는 AndroidPlatform의 인스턴스를 생성하고 이를 application() 함수에 전달해야 하며, iOS에서는 유사하게 IOSPlatform의 인스턴스를 생성하고 전달해야 합니다. 이러한 진입점은 애플리케이션의 진입점이 될 필요는 없지만, 이곳에서 공유 모듈의 특정 기능을 호출할 수 있습니다.

예상 및 실제 함수를 사용하거나 진입점을 통해 직접 올바른 구현을 제공하는 것은 단순한 시나리오에 잘 작동합니다. 그러나 프로젝트에서 의존성 주입(DI) 프레임워크를 사용하는 경우, 일관성을 보장하기 위해 단순한 경우에도 사용하는 것을 권장합니다.

의존성 주입 프레임워크

최신 애플리케이션은 일반적으로 느슨하게 결합된 아키텍처를 만들기 위해 의존성 주입(DI) 프레임워크를 사용합니다. DI 프레임워크는 현재 환경에 따라 컴포넌트에 종속성을 주입할 수 있도록 합니다.

Kotlin Multiplatform을 지원하는 모든 DI 프레임워크는 다양한 플랫폼에 대해 다른 종속성을 주입하는 데 도움이 될 수 있습니다.

예를 들어, Koin은 Kotlin Multiplatform을 지원하는 의존성 주입 프레임워크입니다.

kotlin
// 공통 소스 세트에서:
import org.koin.dsl.module

interface Platform

expect val platformModule: Module
kotlin
// androidMain 소스 세트에서:
class AndroidPlatform : Platform

actual val platformModule: Module = module {
    single<Platform> {
        AndroidPlatform()
    }
}
kotlin
// iosMain 소스 세트에서:
class IOSPlatform : Platform

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

여기서 Koin DSL은 주입을 위한 컴포넌트를 정의하는 모듈을 생성합니다. expect 키워드를 사용하여 공통 코드에 모듈을 선언한 다음, actual 키워드를 사용하여 각 플랫폼에 대한 플랫폼별 구현을 제공합니다. 프레임워크는 런타임에 올바른 구현을 선택하는 것을 처리합니다.

DI 프레임워크를 사용할 때, 이 프레임워크를 통해 모든 종속성을 주입합니다. 동일한 로직이 플랫폼 종속성 처리에도 적용됩니다. 프로젝트에 DI가 이미 있다면 예상 및 실제 함수를 수동으로 사용하는 것보다 계속 DI를 사용하는 것을 권장합니다. 이렇게 하면 두 가지 다른 종속성 주입 방식을 혼합하는 것을 피할 수 있습니다.

또한 공통 인터페이스를 항상 Kotlin으로 구현할 필요는 없습니다. Swift와 같은 다른 언어로 다른 _플랫폼 모듈_에서 할 수 있습니다. 이 접근 방식을 선택하면 DI 프레임워크를 사용하여 iOS 플랫폼 모듈에서 구현을 제공해야 합니다.

의존성 주입 프레임워크 사용

이 접근 방식은 구현을 플랫폼 모듈에 배치할 때만 작동합니다. Kotlin Multiplatform 모듈이 자체적으로 충분할 수 없고, 다른 모듈에서 공통 인터페이스를 구현해야 하므로 확장성이 좋지 않습니다.

다음 단계는?