← Назад к вопросам

Как работает actual?

1.0 Junior🔥 132 комментариев
#Kotlin основы

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Краткий ответ на вопрос об actual в Kotlin Multiplatform (KMP)

В Kotlin Multiplatform ключевые слова expect и actual работают в паре для объявления и реализации платформенно-зависимого кода. Это механизм, который позволяет писать общую логику (common code) и при этом предоставлять специфичные реализации для каждой целевой платформы (например, Android, iOS, JVM, JS).

Основной принцип работы actual

  1. В общем модуле (common) вы объявляете ожидаемую (expect) декларацию (класс, функцию, свойство или даже object). В этом месте вы лишь описываете, что должно быть, но не реализуете, как это работает.
  2. В каждом платформенном модуле (например, androidMain, iosMain, jvmMain) вы предоставляете фактическую (actual) реализацию этой декларации, подходящую именно для данной операционной системы или среды выполнения.
  3. Компилятор 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, позволяющий писать действительно переиспользуемую бизнес-логику, сохраняя при этом нативный доступ ко всем возможностям каждой операционной системы.