expect/actual — ماهیت، کلمات کلیدی KMM و نحوه کار

نویسنده: IT Sectr منتشر شده: 2026-06-05 زمان مطالعه: 8 دقیقه

expect/actual — مکانیزم Kotlin Multiplatform است که امکان اعلان APIهای وابسته به پلتفرم را در کد مشترک فراهم می‌کند. کلمه کلیدی expect یک قرارداد برای تابع، کلاس یا ویژگی در commonMain ایجاد می‌کند و کلمه کلیدی actual پیاده‌سازی مشخصی برای هر پلتفرم ارائه می‌دهد. کامپایلر بررسی می‌کند که برای هر اعلان expect یک پیاده‌سازی actual در تمام پلتفرمهای هدف وجود داشته باشد. به گفته JetBrains, 2025، این مکانیزم در 80% پروژه‌های KMM برای پیاده‌سازی منطق تجاری پلتفرم استفاده می‌شود.

نکات اصلی

  • expect — کلمه کلیدی برای اعلان قرارداد تابع، کلاس یا ویژگی در کد مشترک.
  • actual — کلمه کلیدی برای ارائه پیاده‌سازی پلتفرمی اعلان expect.
  • commonMain — source set با کد مشترک که اعلان‌های expect در آن قرار دارند.
  • بررسی کامپایلر — کامپایلر وجود پیاده‌سازی‌های actual را برای تمام پلتفرم‌های هدف تضمین می‌کند.
  • Source set — مجموعه‌هایی (iosMain، androidMain) که پیاده‌سازی‌های پلتفرمی actual در آن قرار دارند.

expect/actual چیست؟

expect/actual — یک مکانیزم اعلانی Kotlin Multiplatform برای پیاده‌سازی برنامه‌نویسی پلتفرم‌محور است. این امکان را می‌دهد که API یک بار در ماژول مشترک (expect) توصیف شود و به طور جداگانه برای هر پلتفرم (actual) پیاده‌سازی شود. بر خلاف اینترفیس‌ها، expect/actual فراخوانی‌های مجازی ایجاد نمی‌کند — کامپایلر اعلان‌های expect و actual را در مرحله کامپیل به هم متصل می‌کند که هزینه‌های اضافی توزیع پویا را حذف می‌کند.

تاریخچه expect/actual با ظهور Kotlin Multiplatform در سال 2017 آغاز شد. در ابتدا این مکانیزم expect/actual declarations نام داشت و آزمایشی بود. در Kotlin 1.2 یادداشت‌های expect اضافه شدند و در Kotlin 1.3 expect/actual برای کلاس‌ها و توابع پایدار شد. به تدریج مکانیزم گسترش یافت: در Kotlin 1.6 پشتیبانی از expect/actual برای اشیای companion، در Kotlin 1.7 — برای کلاس‌های enum، و در Kotlin 2.0 — برای typealias اضافه شد.

ویژگی کلیدی expect/actual — ایمنی در سطح کامپایلر. اگر توسعه‌دهنده یک اعلان expect به commonMain اضافه کند اما پیاده‌سازی actual را برای iOS فراموش کند، کامپایلر خطا می‌دهد. این کار از خطاهای runtime که برای رویکردهای بازتابی یا بارگذاری پویای کد پلتفرم مشخص هستند جلوگیری می‌کند.

مکانیزم expect/actual چگونه کار می‌کند

مکانیزم expect/actual در سطح source set — سیستم ماژول Kotlin Multiplatform — کار می‌کند. کد مشترک قابل دسترس برای تمام پلتفرم‌ها در source set commonMain قرار دارد. کد وابسته به پلتفرم — در iosMain، androidMain، macosMain و غیره. کلمه کلیدی expect در commonMain API را اعلان می‌کند و کلمه کلیدی actual در source set پلتفرم پیاده‌سازی را ارائه می‌دهد. کامپایلر آنها را در مرحله تولید کد به هم متصل می‌کند و فراخوانی تابع expect را با پیاده‌سازی actual مربوطه برای پلتفرم هدف جایگزین می‌کند.

سلسله‌مراتب source set در یک پروژه KMM معمولی به این صورت است: commonMain شامل اعلان‌های expect، iosMain و androidMain شامل پیاده‌سازی‌های actual هستند. در کامپیل برای iOS actual از iosMain استفاده می‌شود، در کامپیل برای Android — از androidMain. Source set‌ها می‌توانند میانی باشند (مثلاً iosArm64Main برای معماری مشخص)، که امکان دقیق‌تر کردن پیاده‌سازی‌ها را برای دستگاه‌های مختلف فراهم می‌کند.

kotlin
// commonMain — اعلان expect
expect fun getPlatformName(): String

// androidMain — actual برای Android
actual fun getPlatformName(): String = "Android"

// iosMain — actual برای iOS
actual fun getPlatformName(): String = "iOS"

بررسی پیاده‌سازی‌های actual توسط کامپایلر

کامپایلر Kotlin چند شرط را هنگام کار با expect/actual بررسی می‌کند. هر اعلان expect باید برای هر پلتفرم فعال یک پیاده‌سازی actual داشته باشد. امضای اعلان actual باید با امضای expect مطابقت داشته باشد (یادداشت @OptionalExpectation می‌تواند این نیاز را نرم کند). تغییردهننده‌های دسترسی، نوع بازگشتی و پارامترها باید یکسان باشند. کامپایلر همچنین عدم وجود وابستگی‌های چرخه‌ای بین اعلان‌های expect و actual را بررسی می‌کند.

انواع expect/actual: توابع، کلاس‌ها، ویژگی‌ها

expect/actual از چند نوع اعلان پشتیبانی می‌کند. پرکاربردترین آنها توابع expect/actual برای عملیات پلتفرمی، کلاس‌های expect/actual برای اشیایی که نیاز به پیاده‌سازی بومی دارند و ویژگی‌های expect/actual برای ثابت‌ها و تنظیمات هستند. هر نوع قوانین استفاده و محدودیت‌های خاص خود را دارد.

توابع expect/actual — ساده‌ترین و رایج‌ترین نوع. آنها برای فراخوانی APIهای پلتفرمی مانند دریافت زمان، خواندن فایل‌ها یا ارسال درخواست‌های HTTP استفاده می‌شوند. کلاس‌های expect/actual برای ایجاد اشیایی که مستقیماً با کد بومی تعامل دارند (مثلاً برای دسترسی به دوربین، مکان‌یابی یا ذخیره‌سازی کلید) به کار می‌روند. ویژگی‌های expect/actual (val) برای ثابت‌های پلتفرمی — نام سیستم‌عامل، نسخه SDK یا مسیر دیرکتوری سیستم — مناسب هستند.

نوع اعلانکلمات کلیدیمثال استفاده
تابعexpect fun / actual funدریافت شناسه یکتای دستگاه
کلاسexpect class / actual classدسترسی به SecureStorage (Keychain / EncryptedSharedPreferences)
ویژگیexpect val / actual valپلتفرم فعلی (iOS / Android)
کلاس enumexpect enum / actual enumلیست مجوزهای موجود برنامه
Typealiasexpect typealias / actual typealiasنوع پاسخ شبکه اختصاصی پلتفرم

محدودیت‌های expect/actual

همه ساختارهای Kotlin نمی‌توانند با expect/actual استفاده شوند. اعلان expect نمی‌تواند بدنه داشته باشد — فقط امضا. کلاس expect نمی‌تواند سازنده با پارامتر داشته باشد (باید سازنده اصلی خالی داشته باشد). برای enum expect/actual همه ثابت‌ها باید در expect و actual یکسان باشند. ویژگی‌های expect باید val باشند (نه var)، زیرا ذخیره وضعیت در ماژول مشترک برای ویژگی‌های پلتفرمی معنی ندارد.

مثال‌های کد: از ساده تا پیچیده

بیایید مثال‌های عملی expect/actual از توابع ساده تا کلاس‌های کامل را بررسی کنیم. حالت پایه — دریافت نام پلتفرم برای استفاده در رابط کاربری. مثال‌های پیچیده‌تر شامل دسترسی به ذخیره‌سازی بومی و کار با رشته‌های پلتفرمی هستند.

kotlin
// commonMain — کلاس expect برای ذخیره‌سازی امن
expect class PlatformStorage {
    fun save(key: String, value: String)
    fun get(key: String): String?
    fun remove(key: String)
}

// androidMain — actual در 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() }
}

در این مثال، کلاس expect PlatformStorage قرارداد یک ذخیره‌سازی ساده کلید-مقدار را تعریف می‌کند. در Android پیاده‌سازی از SharedPreferences و در iOS از Keychain یا NSUserDefaults استفاده می‌کند. به لطف expect/actual، منطق تجاری در commonMain بدون آگاهی از پیاده‌سازی پلتفرمی، save/get/remove را فراخوانی می‌کند.

kotlin
// iosMain — actual در iOS با 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)
    }
}

بهترین روش‌های expect/actual

هنگام طراحی API expect/actual باید چند اصل را رعایت کرد. تعداد اعلان‌های expect را به حداقل برسانید — هرچه کد مشترک بیشتر باشد، نگهداری آسان‌تر است. از expect/actual فقط برای APIهایی استفاده کنید که واقعاً در پلتفرم‌ها متفاوت هستند. برای باقی کد از اینترفیس‌ها با کارخانه‌ها یا تزریق وابستگی استفاده کنید که تستینگ را ساده‌تر می‌کند.

توصیه می‌شود اعلان‌های expect را بر اساس ماژول‌های موضوعی گروه‌بندی کنید، نه آنها را در یک فایل مخلوط کنید. مثلاً Storage.kt برای اعلان‌های expect ذخیره‌سازی، Platform.kt برای توابع expect کار با سیستم‌عامل و Analytics.kt برای کلاس‌های expect تحلیل. این کار پیمایش و درک سطح پلتفرمی پروژه KMM را ساده‌تر می‌کند. هر فایل actual باید در source set مربوطه قرار گیرد: androidMain، iosMain، desktopMain و غیره.

پیاده‌سازی‌های پیش‌فرض از طریق expect fun با actual fun که actual از کد مشترک استفاده می‌کند — یک الگوی ضد رایج. اگر پیاده‌سازی پلتفرمی با پیش‌فرض تفاوتی ندارد، expect/actual لازم نیست. در چنین مواردی از یک تابع ساده در commonMain استفاده کنید. همچنین از expect/actual برای getterهای پیش‌پاافتاده خودداری کنید — از expect val با ثابت‌ها استفاده کنید.

سازماندهی کد در پروژه

ساختار صحیح کد expect/actual برای خوانایی پروژه حیاتی است. هر ماژول expect/actual باید یک نقطه ورود واحد داشته باشد. مثال سازماندهی: commonMain/kotlin/com/project/platform شامل اعلان‌های expect، androidMain/kotlin/com/project/platform — actual برای Android، iosMain/kotlin/com/project/platform — actual برای iOS. نام فایل‌ها و بسته‌ها باید برای expect و actual یکسان باشند تا توسعه‌دهنده بتواند به سرعت پیاده‌سازی مربوطه را پیدا کند.

جایگزین‌های expect/actual در KMM

اینترفیس‌ها با کارخانه پلتفرمی — جایگزین اصلی expect/actual. به جای کلاس expect می‌توان یک اینترفیس در commonMain اعلان کرد و کلاس‌های مشخص در ماژول‌های پلتفرمی ایجاد کرد. کارخانه یا کانتینر تزریق وابستگی پیاده‌سازی صحیح را در runtime ارائه می‌دهد. این رویکرد برای تستینگ مناسب‌تر است زیرا اینترفیس را می‌توان mock کرد.

تزریق وابستگی (Koin، Kodein) — رویکردی انعطاف‌پذیرتر اما کم‌بازده‌تر. کانتینر DI به طور جداگانه برای هر پلتفرم پیکربندی می‌شود و وابستگی‌های پلتفرمی را به کد مشترک ارائه می‌دهد. بر خلاف expect/actual، تزریق در runtime انجام می‌شود که امکان تعویض پیاده‌سازی‌ها را برای تستینگ فراهم می‌کند. از طرف دیگر، خطاهای پیکربندی DI فقط در زمان اجرا کشف می‌شوند، نه در مرحله کامپیل.

رویکردبررسی در مرحله کامپیلانعطاف‌پذیری تستینگهزینه اضافی runtime
expect/actualکاملکم (actual قابل mock نیست)صفر (اتصال کامپیل)
اینترفیس‌ها + کارخانهجزئیزیاد (قابل mock)حداقل (فراخوانی مجازی)
تزریق وابستگیخیر (runtime)زیادمتوسط (DI پراکسی)

انتخاب بین expect/actual و جایگزین‌ها به زمینه بستگی دارد. برای عملکرد بهینه (موتورهای بازی، پردازش بلادرنگ) expect/actual به دلیل هزینه صفر runtime ترجیح دارد. برای منطق تجاری (مخزن‌ها، use caseها) بهتر است از اینترفیس‌ها با DI استفاده شود تا تستینگ ساده‌تر شود. رویکرد ترکیبی — expect/actual برای عملیات پلتفرمی سطح پایین و اینترفیس‌ها برای لایه منطق تجاری — در اکثر پروژه‌های تولیدی KMM استفاده می‌شود.

سوالات متداول

تفاوت expect/actual با اینترفیس‌ها چیست؟

expect/actual پیاده‌سازی را در مرحله کامپیل بدون فراخوانی‌های مجازی متصل می‌کند، در حالی که اینترفیس‌ها — در runtime. expect/actual وجود پیاده‌سازی را برای همه پلتفرم‌ها تضمین می‌کند، اینترفیس‌ها نیاز به بررسی‌های runtime دارند.

آیا می‌توان از expect/actual برای enum استفاده کرد؟

بله، expect enum از Kotlin 1.7 پشتیبانی می‌شود. همه ثابت‌ها در expect و actual enum باید مطابقت داشته باشند. مقادیر مختلف ثابت‌ها در پلتفرم‌های مختلف — خطای کامپیل.

اگر پیاده‌سازی actual فراموش شود چه اتفاقی می‌افتد؟

کامپایلر برای هر پلتفرمی که پیاده‌سازی actual ندارد خطا می‌دهد. پروژه تا زمانی که برای همه اعلان‌های expect پیاده‌سازی‌های actual مربوطه اضافه نشوند، ساخته نخواهد شد.

آیا می‌توان از expect/actual در یک source set استفاده کرد؟

خیر، expect و actual باید در source set‌های مختلف باشند. expect — در commonMain یا source set میانی، actual — در source set پلتفرمی. قرار دادن expect و actual در یک source set خطای کامپیل است.

چگونه کد expect/actual را test کنیم؟

برای تستینگ expect/actual از commonTest با source set‌های test پلتفرمی استفاده کنید. تست‌های expect را در commonTest و تست‌های actual را برای هر پلتفرم بنویسید. تست‌های یکپارچگی به طور جداگانه روی هر پلتفرم هدف اجرا می‌شوند.

خلاصه

  • expect/actual — مکانیزم کلیدی Kotlin Multiplatform برای پیاده‌سازی‌های پلتفرمی با بررسی کامپایلر.
  • expect قرارداد را در commonMain اعلان می‌کند، actual پیاده‌سازی را در source set پلتفرمی ارائه می‌دهد.
  • انواع اعلان شامل توابع، کلاس‌ها، ویژگی‌ها، کلاس‌های enum و typealias با قوانین مختلف استفاده هستند.
  • بررسی کامپایلر وجود پیاده‌سازی‌های actual را برای همه پلتفرم‌های هدف تضمین می‌کند و از خطاهای runtime جلوگیری می‌کند.
  • توصیه می‌شود expect/actual را به حداقل برسانید و برای منطق تجاری از اینترفیس‌ها با DI استفاده کنید.
  • سازماندهی کد باید یکنواخت با نام فایل‌ها و بسته‌های یکسان برای expect و actual باشد.
  • از expect/actual برای عملیات پلتفرمی سطح پایین (ذخیره‌سازی، سیستم فایل، حسگرها) استفاده کنید — این هزینه صفر runtime را تضمین می‌کند.

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید