Kotlin MultiplatformJuniorTechnical

Что такое структура source set: commonMain, androidMain, iosMain?

Source sets — это логические наборы исходников с зависимостями. commonMain компилируется для всех платформ, androidMain/iosMain — только для своей. Иерархия настраивается в kotlin {} блоке build.gradle.kts.

Что такое source set

Source set — это именованный набор директорий с кодом (kotlin/) и ресурсами (resources/), плюс список зависимостей. Каждый source set компилируется для определённого набора таргетов. Ключевое свойство: source set может зависеть от другого (директива dependsOn), формируя иерархию.

Стандартная иерархия source sets

kotlin {
    androidTarget()
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    //  Иерархия формируется автоматически при использовании
    //  стандартных имён. Вручную её можно расширить:
    sourceSets {
        // Базовый — компилируется для ВСЕХ платформ:
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
            }
        }

        // Только для Android:
        val androidMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-okhttp:2.3.12")
            }
        }

        // Промежуточный для ВСЕХ iOS-таргетов:
        val iosMain by creating {
            dependsOn(commonMain)
            dependencies {
                implementation("io.ktor:ktor-client-darwin:2.3.12")
            }
        }

        // Конкретные iOS-таргеты наследуют iosMain:
        val iosX64Main by getting { dependsOn(iosMain) }
        val iosArm64Main by getting { dependsOn(iosMain) }
        val iosSimulatorArm64Main by getting { dependsOn(iosMain) }
    }
}

Структура директорий на диске

shared/
  src/
    commonMain/
      kotlin/
        com/example/shared/
          models/User.kt          // доступно везде
          repos/UserRepository.kt
      resources/
    androidMain/
      kotlin/
        com/example/shared/
          actual/PlatformUtils.android.kt  // actual-реализация
    iosMain/
      kotlin/
        com/example/shared/
          actual/PlatformUtils.ios.kt      // actual-реализация
    commonTest/
      kotlin/
        com/example/shared/
          UserRepositoryTest.kt  // тесты запустятся на всех платформах
    androidUnitTest/
      kotlin/
    iosTest/
      kotlin/

expect/actual в контексте source sets

// commonMain: объявляем ожидание
expect class PlatformLogger {
    fun log(message: String)
}

// androidMain: даём реализацию
actual class PlatformLogger {
    actual fun log(message: String) {
        android.util.Log.d("App", message)
    }
}

// iosMain: даём реализацию
actual class PlatformLogger {
    actual fun log(message: String) {
        println(message)  // NSLog через println в Kotlin/Native
    }
}

Kotlin 1.9.20+ Hierarchy Template

Начиная с Kotlin 1.9.20, при использовании стандартных имён таргетов (iosX64, iosArm64, iosSimulatorArm64) промежуточный iosMain создаётся автоматически. Явный dependsOn больше не нужен:

kotlin {
    androidTarget()
    iosX64()
    iosArm64()
    iosSimulatorArm64()

    // iosMain создаётся автоматически — код пишем сюда:
    sourceSets {
        iosMain.dependencies {
            implementation("io.ktor:ktor-client-darwin:2.3.12")
        }
    }
}

Подводные камни

  • Написать actual-реализацию в iosX64Main вместо iosMain — при добавлении iosArm64 компиляция упадёт с ошибкой «expect has no actual»;
  • Добавить Android-зависимость в commonMain — iOS-таргет не скомпилируется; зависимость обязана идти в androidMain;
  • Забыть про commonTest и писать тесты только в jvmTest — логика в commonMain остаётся непротестированной на iOS;
  • Создавать слишком много промежуточных source sets (например, mobileMain) без реальной необходимости — усложняет граф зависимостей;
  • Дублировать код в iosX64Main и iosArm64Main вместо iosMain — нарушает принцип единственного источника истины;
  • Использовать by creating вместо by getting для уже существующего source set — Gradle создаёт новый пустой, игнорируя существующий.

Common mistakes

  • Объяснять «source sets commonMain/androidMain/iosMain» только как синтаксис и не описывать поведение runtime/compiler.
  • Игнорировать важный риск: Путаница source sets часто проявляется как unresolved reference или скрытая platform dependency в common коде.
  • Давать пример без edge case: отмены, null, recomposition, platform boundary или ошибки.

What the interviewer is testing

  • Формулирует суть темы «source sets commonMain/androidMain/iosMain» своими словами и связывает ее с кодом.
  • Называет механизм: commonMain компилируется во все targets, androidMain видит Android API, iosMain видит Apple/Native API.
  • Видит production-последствие: Путаница source sets часто проявляется как unresolved reference или скрытая platform dependency в common коде.

Sources

Related topics