@MainActor — چیست، کاربردها و ویژگی‌ها در کد ناهمزمان Swift

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

@MainActor یک اکشنر سراسری در زبان Swift است که اجرای کد را روی نخ اصلی تضمین می‌کند. بر اساس Apple Developer, 2024، @MainActor جابه‌جایی به نخ اصلی را هنگام کار با UI خودکار می‌کند و برنامه‌نویس را از فراخوانی دستی DispatchQueue.main.async بی‌نیاز می‌کند. این حاشیه‌نویسی در Swift 5.5 همراه با سیستم async/await معرفی شد.

نکات کلیدی

  • @MainActor — اکشنر سراسری Swift برای تضمین اجرا روی نخ اصلی.
  • سیستم async/await — پایه‌ای که کار @MainActor بر آن بنا شده است.
  • حاشیه‌نویسی کلاس به‌طور خودکار همه متدهای آن را روی نخ اصلی قرار می‌دهد.
  • برخلاف DispatchQueue.main، @MainActor نخ را در سطح کامپایلر بررسی می‌کند.
  • به‌روزرسانی‌های UI — حوزه اصلی کاربرد @MainActor در توسعه iOS.

@MainActor چیست؟

@MainActor یک اکشنر سراسری (global actor) در Swift است که ویژگی‌های اکشنرها را با تضمین اجرا روی نخ اصلی برنامه ترکیب می‌کند. این بخشی از سیستم همروندی Swift است که در Swift 5.5 همراه با async/await و همروندی ساختاریافته معرفی شد. این حاشیه‌نویسی به برنامه‌نویس اجازه می‌دهد به جابه‌جایی نخ‌ها به‌صورت دستی فکر نکند و تعداد خطاهای UI را کاهش می‌دهد.

تعریف و جایگاه در Swift Concurrency

اکشنر در Swift یک نوع ارجاعی است که حالت خود را ایزوله می‌کند و تضمین می‌کند فقط یک نخ می‌تواند آن را تغییر دهد. @MainActor یک اکشنر سراسری ویژه است که اجراکننده آن نخ اصلی است. هر کدی که با @MainActor مشخص شده باشد روی نخ اصلی اجرا می‌شود — حتی اگر از یک وظیفه پس‌زمینه فراخوانی شود.

پیش از ظهور @MainActor، توسعه‌دهندگان به‌صورت دستی از طریق DispatchQueue.main.async به نخ اصلی جابه‌جا می‌شدند. این منبع خطاهای مکرر بود: توسعه‌دهندگان فراموش می‌کردند جابه‌جا شوند که به crash به دلیل به‌روزرسانی UI در نخ غیراصلی منجر می‌شد. @MainActor این مشکل را در سطح سیستم انواع حل می‌کند.

دلایل ایجاد

منبع بیشتر باگ‌ها در برنامه‌های iOS، ناامنی UI است — به‌روزرسانی رابط از نخ پس‌زمینه. اپل @MainActor را در Swift Concurrency تعبیه کرد تا جابه‌جایی به نخ اصلی را خودکار و قابل بررسی توسط کامپایلر کند و یک کلاس کامل از خطاهای runtime را از بین ببرد.

@MainActor چگونه کار می‌کند؟

اصل کار @MainActor بر سیستم اجرای Swift Concurrency استوار است. وقتی یک نخ تابعی را فراخوانی می‌کند که با @MainActor مشخص شده، زمان‌بند آن را روی اجراکننده فعلی معلق می‌کند و روی نخ اصلی از سر می‌گیرد. کامپایلر مرزهای فراخوانی را دنبال می‌کند و ایمنی را تضمین می‌کند.

اجراکننده نخ اصلی

مسئول اجرای @MainActor MainActor.shared است — اجراکننده‌ای که با نخ اصلی برنامه مرتبط است. وقتی یک تابع ناهمزمان با @MainActor مشخص می‌شود، همیشه روی این اجراکننده از سر گرفته می‌شود، صرف‌نظر از اینکه وظیفه اصلی روی چه نخی شروع شده باشد.

swift
import SwiftUI

class ViewModel: ObservableObject {
    @Published var items: [String] = []

    @MainActor
    func loadData() async {
        let result = await fetchRemoteData()
        items = result  // ایمن، MainActor نخ اصلی را تضمین می‌کند
    }
}

ارث‌بری زمینه اکشنر

اگر تابعی با @MainActor مشخص شده باشد و تابع ناهمزمان دیگری را فراخوانی کند، به‌طور پیش‌فرض زمینه اکشنر را به ارث می‌برد. این یعنی همه فراخوانی‌های تو در تو نیز روی نخ اصلی اجرا می‌شوند، مگر اینکه خلاف آن مشخص شود. کامپایلر این را دنبال می‌کند و هنگام تلاش برای انتقال یک closure ناسازگار خطا می‌دهد.

مقایسه @MainActor با DispatchQueue.main

مقایسه @MainActor و DispatchQueue.main به درک اینکه چرا مکانیزم جدید ایمن‌تر و راحت‌تر تلقی می‌شود کمک می‌کند، اگرچه هر دو یک کار را حل می‌کنند — اجرای کد روی نخ اصلی.

ایمنی در سطح انواع

@MainActor بررسی در سطح کامپایلر است. اگر بخواهید تابع @MainActor را از یک زمینه ناامن فراخوانی کنید، کامپایلر هشدار یا خطا صادر می‌کند. DispatchQueue.main.async یک فراخوانی runtime است: کد کامپایل می‌شود، اما ممکن است در هنگام به‌روزرسانی UI از نخ پس‌زمینه در runtime سقوط کند.

کارایی و سربار

DispatchQueue.main.async یک بلوک به صف اضافه می‌کند که ممکن است با تأخیر اجرا شود. @MainActor با async/await جابه‌جایی مستقیم اجراکننده را بدون ایجاد closure اضافی انجام می‌دهد. این سربار را کاهش می‌دهد و کد را از نظر زمان اجرا قابل پیش‌بینی‌تر می‌کند.

swift
// رویکرد قدیمی
DispatchQueue.main.async {
    self.updateUI()
}

// رویکرد جدید با @MainActor
@MainActor
func updateUI() {
    // روی نخ اصلی اجرا می‌شود
    self.label.text = "به‌روزرسانی شد"
}
معیار@MainActorDispatchQueue.main
بررسیکامپایلرruntime
نحوحاشیه‌نویسی (اعلانی)فراخوانی (امری)
سربارکم (جابه‌جایی اجراکننده)متوسط (closure + صف)
قابلیت تستبالا (MainActor.shared قابل جایگزینی است)پایین (مک کردن دشوار است)

استفاده از @MainActor در پروژه‌های iOS

در پروژه‌های واقعی iOS، @MainActor در لایه‌های ViewModel، ویوهای SwiftUI و کنترل‌کننده‌های UIKit استفاده می‌شود. حاشیه‌نویسی را می‌توان هم به متدهای جداگانه و هم به کل تیپ به‌صورت یکجا اعمال کرد.

حاشیه‌نویسی کلاس یا ساختار

با مشخص کردن کلاس با @MainActor تضمین می‌کنید که همه متدها و ویژگی‌های آن فقط روی نخ اصلی در دسترس هستند. این به‌ویژه برای ویوهای SwiftUI و کلاس‌های ObservableObject راحت است: فقط @MainActor را قبل از class اضافه می‌کنید و همه ویژگی‌های @Published به‌صورت ایمن به‌روزرسانی می‌شوند.

swift
@MainActor
final class UserListViewModel: ObservableObject {
    @Published var users: [User] = []
    @Published var isLoading = false

    func fetchUsers() async {
        isLoading = true
        users = await api.getUsers()
        isLoading = false
    }
}

پوشش کد legacy

هنگام کار با کد قدیمی در UIKit که در آن جابه‌جایی نخ‌ها دستی بود، می‌توانید از MainActor.run برای جابه‌جایی صریح استفاده کنید. این برای انتقال تدریجی به Swift Concurrency بدون بازنویسی کل پایگاه کد راحت است.

swift
await MainActor.run {
    self.tableView.reloadData()
}

محدودیت‌های @MainActor

با وجود همه مزایا، @MainActor محدودیت‌هایی دارد که هنگام طراحی معماری برنامه باید در نظر گرفته شوند. درک مرزهای کاربرد به جلوگیری از استفاده نادرست کمک می‌کند.

کارایی در استفاده فشرده

اگر کل زنجیره فراخوانی با @MainActor مشخص شود، هر کار سنگینی روی نخ اصلی اجرا می‌شود و باعث کندی UI می‌شود. توصیه می‌شود فقط لایه UI را با @MainActor مشخص کنید و منطق کسب‌وکار و درخواست‌های شبکه‌ای را در اکشنرهای پس‌زمینه یا اجراکننده سراسری نگه دارید.

ناسازگاری با برخی APIها

APIهای قدیمی مبتنی بر callback (مثلاً URLSession بدون async/await) از زمینه اکشنر پشتیبانی نمی‌کنند. برای یکپارچه‌سازی، یک پوشش با CheckedContinuation لازم است. همچنین @MainActor با performSelector، target-action و سایر الگوهای غیرناهمزمان UIKit سازگار نیست.

اشکال‌زدایی چندنخی

در اشکال‌زدایی برنامه‌های با @MainActor بازتولید شرایط رقابت (race conditions) دشوارتر است، زیرا کامپایلر بسیاری از آن‌ها را در مرحله ساخت جلوگیری می‌کند، نه در runtime. با این حال، این ممکن است احساس امنیت کاذب ایجاد کند: کار نادرست با اشیای mutable مشترک (مثلاً NSCache یا متغیرهای سراسری مشترک) همچنان ممکن است، اگر با @MainActor مشخص نشده باشند و بدون همگام‌سازی صریح استفاده شوند.

تست @MainActor

@MainActor تست منطق UI را به‌طور قابل توجهی ساده می‌کند، زیرا نیاز به جابه‌جایی دستی نخ‌ها در تست‌ها را از بین می‌برد. با این حال ویژگی‌هایی وجود دارد که هنگام نوشتن تست‌های واحد و تست‌های UI باید در نظر گرفته شوند.

تست‌های واحد با MainActor

در XCTest محیط اجرا به‌طور خودکار اجراکننده نخ اصلی را پیکربندی می‌کند. وقتی متد تست روی نخ اصلی اجرا می‌شود، فراخوانی تابع‌های @MainActor به تنظیمات اضافی نیاز ندارد — آن‌ها در همان زمینه اجرا می‌شوند. برای تست سناریوهای پس‌زمینه، از MainActor.run داخل Task با مشخص کردن صریح اولویت و اجراکننده استفاده کنید و جداگانه بررسی کنید که کد هنگام فراخوانی از پس‌زمینه به‌درستی کار می‌کند.

یکی از رویکردهای رایج، تست ViewModel با @MainActor است، که در آن بررسی می‌شود ویژگی‌های @Published پس از عملیات ناهمزمان به‌درستی به‌روزرسانی می‌شوند. به لطف ارث‌بری زمینه اکشنر، فراخوانی await داخل تست اجرا روی نخ اصلی را بدون تضمین‌های اضافی DispatchQueue و جابه‌جایی دستی زمینه‌ها تضمین می‌کند، که نوشتن تست‌ها را ساده می‌کند.

بررسی ایزوله‌سازی هنگام بازسازی کد

هنگام بازسازی کد موجود به Swift Concurrency، ایزوله‌سازی @MainActor را از طریق کامپایلر بررسی کنید: هر فراخوانی متد همزمان بدون @MainActor از زمینه @MainActor به‌عنوان خطا علامت‌گذاری می‌شود. این ویژگی برای انتقال تدریجی پروژه به async/await استفاده می‌شود: لایه ViewModel را با @MainActor مشخص می‌کنید و کامپایلر همه فراخوانی‌های ناامن را که باید به اکشنرهای پس‌زمینه منتقل شوند برجسته می‌کند.

Mocking و زمینه Actor

هنگام ایجاد mock برای وابستگی‌های @MainActor، از پروتکل‌هایی با متدهای async استفاده کنید که تابع‌های ناهمزمان با انواع بازگشتی اعلام می‌کنند. این امکان جایگزینی سرویس‌های شبکه‌ای، پایگاه‌های داده و سایر وابستگی‌های خارجی را بدون نقض ایزوله‌سازی اکشنر فراهم می‌کند. کامپایلر بررسی می‌کند که mock همه الزامات ایزوله‌سازی را پیاده‌سازی کرده باشد و از دسترسی تصادفی به کد @MainActor از نخ‌های تست پس‌زمینه جلوگیری می‌کند.

Wait برای انتظار عملیات ناهمزمان

در تست همزمان کد @MainActor، از XCTestExpectation برای انتظار اتمام عملیات ناهمزمان استفاده کنید. انتظار را در تست تنظیم کنید و fulfillment را داخل closure که روی نخ اصلی اجرا می‌شود انجام دهید. اگر تست بی‌نهایت معلق بماند — احتمالاً فراخوانی روی نخ اصلی رخ نمی‌دهد و باید ایزوله‌سازی اکشنر را بررسی کنید. برای اشکال‌زدایی زمینه اجرا، مفید است بررسی Thread.isMainThread را داخل کد تست اضافه کنید.

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

آیا لازم است کل کلاس با @MainActor مشخص شود؟

خیر، کافی است فقط متدهایی را مشخص کنید که UI را به‌روزرسانی می‌کنند. با این حال، اگر در کلاس چند متد از این قبیل وجود داشته باشد، افزودن @MainActor به کل کلاس ساده‌تر است. این تضمین می‌کند همه اعضای آن روی نخ اصلی اجرا شوند و نگهداری کد را ساده می‌کند.

@MainActor چه تفاوتی با @globalActor دارد؟

@MainActor یک نمونه مشخص از اکشنر سراسری است که به نخ اصلی وابسته است. @globalActor پروتکلی برای ایجاد اکشنرهای سراسری خودتان است. مثلاً می‌توانید @BackgroundActor را برای اجرای کد روی نخ پس‌زمینه ایجاد کنید، اگر معماری پروژه آن را ایجاب کند.

آیا می‌توان @MainActor را بدون async/await استفاده کرد؟

بله، تابع‌های همزمان با @MainActor نیز روی نخ اصلی اجرا می‌شوند. با این حال ارزش اصلی @MainActor دقیقاً با async/await نمایان می‌شود، وقتی تابع ناهمزمان به‌طور خودکار بدون جابه‌جایی دستی از طریق DispatchQueue.main روی نخ اصلی از سر گرفته می‌شود.

چگونه یک وظیفه @MainActor را لغو کنیم؟

Task.cancel() با وظایف @MainActor همانند وظایف معمولی کار می‌کند. وظیفه @MainActor می‌تواند Task.isCancelled را بررسی کند یا CancellationError را throw کند. هنگام لغو، نخ اصلی مسدود نمی‌شود — وظیفه فقط در نزدیک‌ترین نقطه تعلیق متوقف می‌شود.

اگر @MainActor از نخ پس‌زمینه فراخوانی شود چه اتفاقی می‌افتد؟

کامپایلر ایمنی را تضمین می‌کند: اگر تابع @MainActor را از زمینه پس‌زمینه فراخوانی کنید، کامپایلر خطا را نشان می‌دهد. برای فراخوانی‌های ناهمزمان کافی است کد فراخواننده را با await مشخص کنید و اجراکننده خودش به نخ اصلی جابه‌جا می‌شود. برای فراخوانی‌های همزمان، جابه‌جایی صریح از طریق MainActor.run لازم است.

خلاصه

  • @MainActor — اکشنر سراسری Swift که اجرا روی نخ اصلی را تضمین می‌کند.
  • بررسی کامپایلر یک کلاس کامل از خطاهای ایمنی UI را از بین می‌برد.
  • حاشیه‌نویسی کل کلاس به‌طور خودکار همه متدهای آن را روی نخ اصلی قرار می‌دهد.
  • MainActor.run — جابه‌جایی صریح برای کد legacy و زمینه‌های همزمان.
  • برخلاف DispatchQueue.main، @MainActor closure ایجاد نمی‌کند و از جابه‌جایی اجراکننده استفاده می‌کند.
  • محاسبات سنگین نباید تحت @MainActor انجام شوند تا از کندی UI جلوگیری شود.
  • ارث‌بری زمینه اکشنر زنجیره‌های فراخوانی ناهمزمان را ساده می‌کند و کد را منسجم، قابل پیش‌بینی و امن برای UI می‌سازد.

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

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

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

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