Механизм expect/actual для API платформ
Объявляйте ожидаемые объявления в общем коде и предоставляйте фактические реализации для каждой платформы.
«Механизм expect/actual для API платформ» — бесплатный урок Kotlin Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Kotlin Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Kotlin Academy содержит 4 уроков всего.
Паттерн ожидаемой и фактической реализации
expect объявляет контракт API в commonMain. Каждая платформа предоставляет реализацию actual. Компилятор проверяет, что для всех целевых платформ существует actual.
// commonMain:
expect fun platformName(): String
// androidMain:
actual fun platformName(): String = "Android ${android.os.Build.VERSION.SDK_INT}"
// iosMain:
actual fun platformName(): String = UIDevice.currentDevice.systemName()Ожидаемый класс
expect class объявляет класс с его API в commonMain. Каждая платформа предоставляет полную реализацию.
// commonMain:
expect class DateTime {
fun format(pattern: String): String
val timestamp: Long
}
// androidMain:
actual class DateTime {
actual val timestamp = System.currentTimeMillis()
actual fun format(pattern: String): String =
java.text.SimpleDateFormat(pattern).format(java.util.Date(timestamp))
}Ожидаемый объект
expect object объявляет одиночный объект с его контрактом. Это удобно для платформенных утилит, например журналирования, настроек или аналитики.
// commonMain:
expect object Logger {
fun debug(msg: String)
fun error(msg: String, cause: Throwable? = null)
}
// androidMain:
actual object Logger {
actual fun debug(msg: String) = android.util.Log.d("App", msg)
actual fun error(msg: String, cause: Throwable?) =
android.util.Log.e("App", msg, cause)
}Ожидаемое свойство или функция верхнего уровня
Ожидаемые свойства expect val и функции expect fun верхнего уровня просты и хорошо подходят для платформенных утилит без состояния.
// commonMain:
expect val isDebugBuild: Boolean
expect fun generateUUID(): String
// androidMain:
actual val isDebugBuild: Boolean = BuildConfig.DEBUG
actual fun generateUUID(): String = java.util.UUID.randomUUID().toString()
// iosMain:
actual val isDebugBuild: Boolean = false // or a native flag
actual fun generateUUID(): String = NSUUID().UUIDString()Ожидаемая функция со стандартной реализацией
Начиная с Kotlin 1.9, expect fun может иметь стандартную реализацию через expect fun f() { }. Платформы могут переопределить её или использовать стандартную реализацию.
// commonMain (Kotlin 1.9+):
expect fun log(msg: String) {
println("[DEFAULT] $msg") // used if no actual overrides
}
// androidMain:
actual fun log(msg: String) = android.util.Log.d("KMP", msg)
// iosMain (uses default):
// no actual needed — default is usedФактический псевдоним типа
Если на платформе уже есть именно тот класс, который нужен, используйте actual typealias, чтобы сослаться на него, вместо создания обёртки.
// commonMain:
expect class AtomicInt {
fun get(): Int
fun incrementAndGet(): Int
}
// androidMain / jvmMain:
actual typealias AtomicInt = java.util.concurrent.atomic.AtomicInteger
// iosMain:
actual class AtomicInt {
private var value = 0
actual fun get() = value
actual fun incrementAndGet() = ++value
}Ожидаемый интерфейс (косвенный паттерн)
Для большей гибкости объявите интерфейс в commonMain, а expect/actual используйте только для фабрики, создающей экземпляр платформы.
// commonMain:
interface FileStorage {
suspend fun readFile(path: String): ByteArray
suspend fun writeFile(path: String, data: ByteArray)
}
expect fun createFileStorage(): FileStorage
// androidMain:
actual fun createFileStorage(): FileStorage = AndroidFileStorage()
// iosMain:
actual fun createFileStorage(): FileStorage = IosFileStorage()Тестирование ожидаемых и фактических реализаций
В commonTest тестируйте общий API. Фактические реализации запускаются во время платформенных задач тестирования (testDebugUnitTest на Android и iosX64Test на iOS).
// commonTest:
import kotlin.test.Test
import kotlin.test.assertNotNull
class PlatformTest {
@Test
fun testUUID() {
val uuid = generateUUID()
assertNotNull(uuid)
println("UUID: $uuid")
}
}Распространённые ошибки
Каждое объявление expect должно иметь actual во всех настроенных целях ALL, иначе сборка завершится с ошибкой. Отсутствующие фактические реализации обнаруживаются во время компиляции.
// If you add a new target (e.g., jvmMain), you must add:
// actual fun platformName(): String = "JVM"
// Otherwise:
// error: Expected function platformName has no actual declaration in module :shared for JVM
fun main() { println("Missing actuals = compile error") }Класс ожидаемой аннотации
expect annotation class позволяет сопоставлять платформенные аннотации — например, сопоставить общую аннотацию, подобную @Parcelize, с плагином Parcelize для Android.
// commonMain:
expect annotation class CommonParcelize()
// androidMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelize
// iosMain:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
actual annotation class CommonParcelizeПрактический пример: Keychain и Preferences
Типичный практический пример expect/actual — интерфейс защищённого хранилища, в котором Android использует EncryptedSharedPreferences, а iOS — Keychain.
// commonMain:
expect class SecureStorage {
fun save(key: String, value: String)
fun get(key: String): String?
}
// androidMain:
actual class SecureStorage {
private val prefs = /* EncryptedSharedPreferences */ mapOf<String,String>()
actual fun save(key: String, value: String) { /* ... */ }
actual fun get(key: String): String? = null // simplified
}Быстрая проверка
Что произойдёт во время компиляции, если для объявления expect не предусмотрена actual для некоторой целевой платформы?
Итоги
Ожидаемая декларация объявляет API в commonMain. Фактическая реализация предоставляет реализацию платформы. Используйте Фактический псевдоним типа, если на платформе уже есть нужный тип. Отсутствующие фактические реализации обнаруживаются во время компиляции — неожиданных проблем во время выполнения не будет.
Часто задаваемые вопросы
Урок «Механизм expect/actual для API платформ» бесплатный?
Да — полный текст урока «Механизм expect/actual для API платформ» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Kotlin Academy, подпишись на CoddyKit PRO. Курс Kotlin Academy содержит 4 уроков всего.
Чему я научусь в уроке «Механизм expect/actual для API платформ»?
Объявляйте ожидаемые объявления в общем коде и предоставляйте фактические реализации для каждой платформы. Ты практикуешь Kotlin Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Kotlin Academy?
Предыдущий опыт не требуется. Kotlin Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Механизм expect/actual для API платформ»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Kotlin Academy?
Да. Каждый урок Kotlin Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Структура проекта KMP: commonMain, androidMain, iosMain
- Механизм expect/actual для API платформ
- Общие слои репозитория и вариантов использования
- Внедрение зависимостей в KMP с Koin