expect/actual — innebörd, KMM-nyckelord och hur de fungerar

Författare: IT Sectr Publicerad: 2026-06-05 Lästid: 8 min

expect/actual — en mekanism i Kotlin Multiplatform som gör det möjligt att deklarera plattformsberoende API:er i gemensam kod. Nyckelordet expect skapar ett kontrakt för en funktion, klass eller egenskap i commonMain, medan nyckelordet actual tillhandahåller en konkret implementering för varje plattform. Kompilatorn kontrollerar att varje expect-deklaration har en motsvarande actual-implementering på alla målplattformar. Enligt JetBrains, 2025 används mekanismen i 80% av KMM-projekt för att implementera plattformsaffärslogik.

Huvudpunkter

  • expect — nyckelord för att deklarera ett kontrakt för en funktion, klass eller egenskap i gemensam kod.
  • actual — nyckelord för att tillhandahålla en plattformsimplementering av en expect-deklaration.
  • commonMain — source set med gemensam kod där expect-deklarationerna finns.
  • Kompilatorkontroll — kompilatorn garanterar att actual-implementeringar finns för alla målplattformar.
  • Source set — samlingar (iosMain, androidMain) där plattformsspecifika actual-implementeringar finns.

Vad är expect/actual?

expect/actual — är en deklarativ mekanism i Kotlin Multiplatform för plattformsorienterad programmering. Den gör det möjligt att beskriva ett API en gång i den gemensamma modulen (expect) och implementera det separat för varje plattform (actual). Till skillnad från gränssnitt skapar expect/actual inga virtuella anrop — kompilatorn kopplar samman expect- och actual-deklarationer vid kompileringstillfället, vilket eliminerar omkostnader för dynamisk dirigering.

Historien om expect/actual började med lanseringen av Kotlin Multiplatform 2017. Från början kallades mekanismen expect/actual declarations och var experimentell. I Kotlin 1.2 lades expect-annotationer till, och i Kotlin 1.3 blev expect/actual stabilt för klasser och funktioner. Gradvis utökades mekanismen: i Kotlin 1.6 lades stöd för expect/actual för companion-objekt till, i Kotlin 1.7 — för enum-klasser, och i Kotlin 2.0 — för typealias.

Den viktigaste egenskapen hos expect/actual är säkerhet på kompileringsnivå. Om en utvecklare lägger till en expect-deklaration i commonMain men glömmer att tillhandahålla en actual-implementering för iOS, ger kompilatorn ett fel. Detta förhindrar körningsfel som är typiska för metoder med reflektion eller dynamisk inläsning av plattformskod.

Hur expect/actual-mekanismen fungerar

Mekanismen expect/actual arbetar på source set-nivå — Kotlin Multiplatforms modulsystem. Gemensam kod som är tillgänglig för alla plattformar finns i source set commonMain. Plattformsberoende kod — i iosMain, androidMain, macosMain och så vidare. Nyckelordet expect i commonMain deklarerar ett API, medan nyckelordet actual i plattformens source set tillhandahåller implementeringen. Kompilatorn kopplar samman dem vid kodgenerering och ersätter anropet till expect-funktionen med motsvarande actual-implementering för målplattformen.

Source set-hierarkin i ett typiskt KMM-projekt ser ut så här: commonMain innehåller expect-deklarationer, iosMain och androidMain innehåller actual-implementeringar. Vid kompilering för iOS används actual från iosMain, vid kompilering för Android — från androidMain. Source set kan vara mellanliggande (t.ex. iosArm64Main för en specifik arkitektur), vilket gör det möjligt att specificera implementeringar för olika enheter.

kotlin
// commonMain — expect-deklaration
expect fun getPlatformName(): String

// androidMain — actual för Android
actual fun getPlatformName(): String = "Android"

// iosMain — actual för iOS
actual fun getPlatformName(): String = "iOS"

Kompilatorkontroll av actual-implementeringar

Kotlin-kompilatorn kontrollerar flera villkor vid arbete med expect/actual. Varje expect-deklaration måste ha en actual-implementering för varje aktiv plattform. Signaturen för actual-deklarationen måste matcha expect-signaturen (annotationen @OptionalExpectation kan mildra detta krav). Åtkomstmodifierare, returtyp och parametrar måste vara identiska. Kompilatorn kontrollerar också frånvaron av cykliska beroenden mellan expect- och actual-deklarationer.

Typer av expect/actual: funktioner, klasser, egenskaper

expect/actual stödjer flera typer av deklarationer. De vanligaste är expect/actual-funktioner för plattformsoperationer, expect/actual-klasser för objekt som kräver inbyggd implementering och expect/actual-egenskaper för konstanter och inställningar. Varje typ har sina egna användningsregler och begränsningar.

Expect/actual-funktioner — den enklaste och mest använda typen. De används för att anropa plattforms-API:er som att hämta tid, läsa filer eller skicka HTTP-förfrågningar. Expect/actual-klasser används för att skapa objekt som direkt interagerar med inbyggd kod (t.ex. för kameraåtkomst, geolokalisering eller säker lagring). Expect/actual-egenskaper (val) är lämpliga för plattformskonstanter — OS-namn, SDK-version eller sökväg till systemkatalog.

DeklarationstypNyckelordAnvändningsexempel
Funktionexpect fun / actual funHämta unik enhetsidentifierare
Klassexpect class / actual classÅtkomst till SecureStorage (Keychain / EncryptedSharedPreferences)
Egenskapexpect val / actual valAktuell plattform (iOS / Android)
Enum-klassexpect enum / actual enumLista över tillgängliga appbehörigheter
Typealiasexpect typealias / actual typealiasPlattformsspecifik nätverkssvartyp

Begränsningar för expect/actual

Alla Kotlin-konstruktioner kan inte användas med expect/actual. En expect-deklaration kan inte ha en kropp — endast signatur. En expect-klass kan inte ha en konstruktor med parametrar (måste ha en tom primärkonstruktor). För enum expect/actual måste alla konstanter vara desamma i expect och actual. Expect-egenskaper måste vara val (inte var), eftersom lagring av tillstånd i den gemensamma modulen för plattformsegenskaper inte är meningsfullt.

Kodexempel: från enkelt till komplext

Låt oss titta på praktiska exempel på expect/actual från enkla funktioner till fullvärdiga klasser. Grundfallet — att hämta plattformsnamnet för användning i UI. Mer komplexa exempel inkluderar åtkomst till inbyggd lagring och arbete med plattformstrådar.

kotlin
// commonMain — expect-klass för säker lagring
expect class PlatformStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun remove(key: String)
}

// androidMain — actual på Android
actual class PlatformStorage {
    private val prefs = AppContext.getSharedPreferences("secure", 0)

    actual fun save(key: String, value: String) { prefs.edit().putString(key, value).apply() }
    actual fun get(key: String): String? = prefs.getString(key, null)
    actual fun remove(key: String) { prefs.edit().remove(key).apply() }
}

I detta exempel definierar expect-klassen PlatformStorage ett kontrakt för en enkel nyckel-värde-lagring. På Android använder implementeringen SharedPreferences, på iOS — Keychain eller NSUserDefaults. Tack vare expect/actual anropar affärslogiken i commonMain save/get/remove utan att känna till plattformsimplementeringen.

kotlin
// iosMain — actual på iOS med Keychain
actual class PlatformStorage {
    actual fun save(key: String, value: String) {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key,
            kSecValueData to value.encodeToByteArray()
        )
        SecItemAdd(query, null)
    }

    actual fun get(key: String): String? {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key,
            kSecReturnData to true
        )
        val result = mutableMapOf<String, Any>()
        return if (SecItemCopyMatching(query, result) == errSecSuccess)
            result[kSecValueData]?.toString()
        else null
    }

    actual fun remove(key: String) {
        val query = mapOf<String, Any>(
            kSecClass to kSecClassGenericPassword,
            kSecAttrAccount to key
        )
        SecItemDelete(query)
    }
}

Bästa praxis för expect/actual

Vid design av expect/actual-API:er bör flera principer följas. Minimera antalet expect-deklarationer — ju mer gemensam kod, desto enklare underhåll. Använd expect/actual endast för de API:er som verkligen skiljer sig mellan plattformar. För resten av koden använd gränssnitt med fabriker eller dependency injection, vilket förenklar testning.

Det rekommenderas att gruppera expect-deklarationer i tematiska moduler istället för att blanda dem i en fil. Till exempel Storage.kt för expect-deklarationer av lagring, Platform.kt för expect-funktioner för OS-arbete och Analytics.kt för expect-klasser för analys. Detta förenklar navigering och förståelse av plattformsytan i ett KMM-projekt. Varje actual-fil bör finnas i motsvarande source set: androidMain, iosMain, desktopMain och så vidare.

Standardimplementeringar via expect fun med actual fun där actual använder gemensam kod är ett vanligt anti-mönster. Om plattformsimplementeringen inte skiljer sig från standarden, behövs inte expect/actual. I sådana fall använd en enkel funktion i commonMain. Undvik också expect/actual för triviala getters — använd expect val med konstanter.

Organisering av kod i projektet

Korrekt struktur för expect/actual-kod är avgörande för projektets läsbarhet. Varje expect/actual-modul bör ha en enda ingångspunkt. Exempel på organisering: commonMain/kotlin/com/project/platform innehåller expect-deklarationer, androidMain/kotlin/com/project/platform — actual för Android, iosMain/kotlin/com/project/platform — actual för iOS. Fil- och paketnamn bör matcha för expect och actual så att utvecklaren snabbt kan hitta motsvarande implementering.

Alternativ till expect/actual i KMM

Gränssnitt med plattformsfabrik — det främsta alternativet till expect/actual. Istället för en expect-klass kan du deklarera ett gränssnitt i commonMain och skapa konkreta klasser i plattformsmoduler. En fabrik eller dependency injection-container tillhandahåller rätt implementering vid körning. Denna metod är bättre lämpad för testning eftersom gränssnittet kan mockas.

Dependency Injection (Koin, Kodein) — ett mer flexibelt men mindre prestandaeffektivt tillvägagångssätt. DI-containern konfigureras separat för varje plattform och tillhandahåller plattformsberoenden till gemensam kod. Till skillnad från expect/actual sker injicering vid körning, vilket gör det möjligt att byta ut implementeringar för testning. Å andra sidan upptäcks DI-konfigurationsfel först vid start, inte vid kompilering.

MetodKontroll vid kompileringTestflexibilitetKörningsomkostnad
expect/actualFullständigLåg (actual kan inte mockas)Ingen (kompileringsbindning)
Gränssnitt + fabrikDelvisHög (kan mockas)Minimal (virtuellt anrop)
Dependency InjectionNej (körning)HögMedel (DI-proxy)

Valet mellan expect/actual och alternativ beror på sammanhanget. För prestandakritiska tillämpningar (spelmotorer, realtidsbearbetning) är expect/actual att föredra på grund av noll omkostnad. För affärslogik (databaser, use-cases) är det bättre att använda gränssnitt med DI för att förenkla testning. En kombinerad metod — expect/actual för lågnivå plattformsoperationer och gränssnitt för affärslogiklagret — används i de flesta produktions-KMM-projekt.

Vanliga frågor

Vad är skillnaden mellan expect/actual och gränssnitt?

expect/actual binder implementeringen vid kompilering utan virtuella anrop, medan gränssnitt gör det vid körning. expect/actual garanterar att implementering finns för alla plattformar, gränssnitt kräver körningskontroller.

Kan jag använda expect/actual för enum?

Ja, expect enum stöds från och med Kotlin 1.7. Alla konstanter i expect och actual enum måste vara identiska. Olika värden på konstanter på olika plattformar är ett kompileringsfel.

Vad händer om jag glömmer en actual-implementering?

Kompilatorn ger ett fel för varje plattform där actual-implementering saknas. Projektet kommer inte att byggas förrän alla expect-deklarationer har motsvarande actual-implementeringar.

Kan jag använda expect/actual inom ett source set?

Nej, expect och actual måste finnas i olika source set. expect — i commonMain eller ett mellanliggande source set, actual — i plattformens source set. Att placera expect och actual i samma source set är ett kompileringsfel.

Hur testar jag expect/actual-kod?

För att testa expect/actual, använd commonTest med plattformsspecifika test-source set. Skriv expect-tester i commonTest och actual-tester för varje plattform. Integrationstester körs separat på varje målplattform.

Sammanfattning

  • expect/actual — en central mekanism i Kotlin Multiplatform för plattformsimplementeringar med kompilatorkontroll.
  • expect deklarerar ett kontrakt i commonMain, actual tillhandahåller implementering i plattformens source set.
  • Deklarationstyper inkluderar funktioner, klasser, egenskaper, enum-klasser och typealias med olika användningsregler.
  • Kompilatorkontroll garanterar att actual-implementeringar finns för alla målplattformar, vilket förhindrar körningsfel.
  • Rekommenderas att minimera expect/actual och använda gränssnitt med DI för affärslogik.
  • Kodorganisation bör vara enhetlig med matchande fil- och paketnamn för expect och actual.
  • Använd expect/actual för lågnivå plattformsoperationer (lagring, filsystem, sensorer) — det säkerställer noll körningsomkostnad.

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också