193просмотров
25.8%от подписчиков
26 марта 2026 г.
📷 ФотоScore: 212
expect/actual в KMP: где граница и почему её легко провести не там Kotlin Multiplatform делит код на общий (commonMain) и платформенный (androidMain, iosMain). Граница между ними — механизм expect/actual. В commonMain объявляешь expect-декларацию, в каждом платформенном модуле пишешь actual-реализацию. // commonMain
expect fun currentTimeMillis(): Long // androidMain
actual fun currentTimeMillis(): Long = System.currentTimeMillis() // iosMain
actual fun currentTimeMillis(): Long = (NSDate().timeIntervalSince1970 * 1000).toLong() Выглядит просто, но на практике большинство проблем в KMP-проектах появляется именно здесь, когда expect/actual используют не по назначению. Самая классическая ошибка — выносить через expect/actual слишком много. Видишь, что на Android и iOS нужна работа с файлами, и пишешь: // commonMain
expect class FileStorage { fun save(name: String, data: ByteArray) fun load(name: String): ByteArray? fun delete(name: String)
} Теперь есть два класса с разной реализацией, которые нужно синхронно поддерживать. Добавил метод — поправил в двух местах. Поменял сигнатуру — снова в двух. А логика вызова FileStorage в бизнес-коде всё равно одинаковая на обеих платформах. Правильный подход: expect/actual только для минимального платформенного примитива, всё остальное — общий код поверх него. // commonMain -- только то, что реально различается
expect fun platformFilesDir(): String // commonMain -- вся логика общая
class FileStorage { fun save(name: String, data: ByteArray) { val path = "${platformFilesDir()}/$name" // запись через общий API }
} На iOS platformFilesDir() вернёт путь через NSFileManager, а на Android через context.filesDir. Второй частый антипаттерн — expect class вместо expect fun. Класс через expect/actual означает две отдельные реализации с разным внутренним состоянием. Это сложнее тестировать и сложнее рефакторить. Функция или интерфейс с actual-фабрикой почти всегда выглядт чище. // commonMain
expect fun createHttpClient(): HttpClient // androidMain
actual fun createHttpClient() = HttpClient(Android) { ... } // iosMain
actual fun createHttpClient() = HttpClient(Darwin) { ... } Хорошее правило: если в expect-декларации больше одного метода, скорее всего, её можно разбить на несколько маленьких expect fun и вынести общую логику в commonMain. Граница expect/actual должна быть как можно у́же. #mobilevk #android #ios #kotlin #kmp