Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Краткий ответ на вопрос об actual в Kotlin Multiplatform (KMP)
В Kotlin Multiplatform ключевые слова expect и actual работают в паре для объявления и реализации платформенно-зависимого кода. Это механизм, который позволяет писать общую логику (common code) и при этом предоставлять специфичные реализации для каждой целевой платформы (например, Android, iOS, JVM, JS).
Основной принцип работы actual
- В общем модуле (common) вы объявляете ожидаемую (
expect) декларацию (класс, функцию, свойство или дажеobject). В этом месте вы лишь описываете, что должно быть, но не реализуете, как это работает. - В каждом платформенном модуле (например,
androidMain,iosMain,jvmMain) вы предоставляете фактическую (actual) реализацию этой декларации, подходящую именно для данной операционной системы или среды выполнения. - Компилятор Kotlin на этапе сборки "связывает"
expectиactualдекларации, обеспечивая корректный вызов платформенной реализации из общего кода.
Детальный пример с кодом
Представьте, что вам нужен доступ к уникальному идентификатору устройства — на каждой платформе он получается по-разному.
1. Объявление в common модуле (commonMain/kotlin/)
Здесь мы объявляем expect класс (или функцию) с нужным интерфейсом.
// commonMain/kotlin/DeviceInfo.kt
package com.example.app
// Ожидаем класс с определенным конструктором и свойством.
// В common коде мы не знаем, как получить ID, но знаем, что он нам нужен.
expect class DeviceInfo() {
val deviceId: String
}
// Ожидаемая функция (альтернативный подход)
expect fun getPlatformName(): String
2. Реализация для Android (androidMain/kotlin/)
В модуле Android мы предоставляем actual реализацию, используя API Android (Settings.Secure).
// androidMain/kotlin/AndroidDeviceInfo.kt
package com.example.app
import android.provider.Settings
// Фактическая реализация для Android.
// Ключевое слово 'actual' соответствует 'expect' из common модуля.
// Имя класса, функции и их сигнатуры должны совпадать.
actual class DeviceInfo actual constructor() {
actual val deviceId: String
get() = Settings.Secure.getString(
android.app.Application().contentResolver,
Settings.Secure.ANDROID_ID
)
}
actual fun getPlatformName(): String = "Android"
3. Реализация для iOS (iosMain/kotlin/)
Для iOS используем actual реализацию на базе iOS Framework.
// iosMain/kotlin/IosDeviceInfo.kt
package com.example.app
import platform.UIKit.UIDevice
import platform.Foundation.NSUUID
actual class DeviceInfo actual constructor() {
// Используем UIDevice для iOS. Обратите внимание: это вызов нативного Swift/Obj-C API.
actual val deviceId: String
get() = UIDevice.currentDevice.identifierForVendor?.UUIDString ?: NSUUID().UUIDString
}
actual fun getPlatformName(): String = "iOS"
4. Использование в общем коде
После того как expect и actual связаны, вы можете спокойно использовать ожидаемую декларацию в общем коде. Компилятор позаботится о том, чтобы в каждом итоговом приложении использовалась правильная платформенная реализация.
// commonMain/kotlin/SomeService.kt
fun logDeviceInfo() {
val info = DeviceInfo() // Создается экземпляр platform-specific класса
println("Running on: ${getPlatformName()}") // Вызывается platform-specific функция
println("Device ID: ${info.deviceId}")
}
Ключевые правила и особенности использования
- Строгое соответствие: Сигнатуры
actualиexpectдеклараций должны полностью совпадать по имени, типу, модификаторам видимости и количеству параметров (для функций). Могут отличаться только аннотации, специфичные для платформы (например,@JvmStaticв JVM-реализации). - Отсутствие дублирования: В
expect-декларации нельзя предоставить тело функции или инициализатор свойства — это лишь контракт. Вся реализация находится вactual-части. - Множественные
actual: Для однойexpect-декларации может существовать несколькоactual-реализаций — по одной на каждую целевую платформу, которую вы поддерживаете. - Иерархия модулей: Механизм работает благодаря особой структуре KMP-проекта. Общий модуль зависит от платформенных, и компилятор разрешает эти связи согласно установленным в
build.gradle.ktsцелевым платформам.
Важное отличие от других подходов
- Противоположность условным директивам
#ifdefиз C++: В KMP платформенно-зависимый код физически находится в разных исходных директориях (androidMain,iosMain), а не перемешивается в одном файле. Это улучшает читаемость и поддерживаемость. - Не абстрактный класс: Хотя концепция похожа на абстрактный класс (
expectкак абстрактный метод,actualкак его реализация), механизмexpect/actualработает на уровне модулей компиляции, а не на уровне наследования в рантайме. Это даёт большую гибкость.
Таким образом, ключевое слово actual — это конкретная, платформенно-ориентированная реализация контракта, объявленного с помощью expect в общем коде. Это краеугольный камень Kotlin Multiplatform, позволяющий писать действительно переиспользуемую бизнес-логику, сохраняя при этом нативный доступ ко всем возможностям каждой операционной системы.