@MainActor — ما هو، التطبيق والميزات في كود Swift غير المتزامن

المؤلف: IT Sectr نُشر: 2026-03-19 وقت القراءة: 8 دق

@MainActor هو ممثل عام في لغة Swift يضمن تنفيذ الكود على الخيط الرئيسي. وفقًا لـ Apple Developer, 2024، يقوم @MainActor بأتمتة التبديل إلى الخيط الرئيسي عند العمل مع واجهة المستخدم، مما يريح المطور من الاستدعاء اليدوي لـ DispatchQueue.main.async. ظهر هذا الوسم في Swift 5.5 مع نظام async/await.

الخلاصة

  • @MainActor — ممثل عام في Swift لضمان التنفيذ على الخيط الرئيسي.
  • نظام async/await — الأساس الذي بُني عليه @MainActor.
  • وسم الفئة يضع تلقائيًا جميع طرقها على الخيط الرئيسي.
  • على عكس DispatchQueue.main، يتحقق @MainActor من الخيط على مستوى المترجم.
  • تحديثات واجهة المستخدم — المجال الرئيسي لاستخدام @MainActor في تطوير iOS.

ما هو @MainActor؟

@MainActor هو ممثل عام في Swift يجمع بين خصائص الممثلين وضمان التنفيذ على الخيط الرئيسي للتطبيق. إنه جزء من نظام التزامن في Swift الذي تم تقديمه في Swift 5.5 مع async/await والتزامن المنظم. يسمح الوسم للمطور بعدم التفكير في التبديل اليدوي للخيوط ويقلل من عدد أخطاء واجهة المستخدم.

التعريف والمكان في Swift Concurrency

الممثل في Swift هو نوع مرجعي يعزل حالته ويضمن أن خيطًا واحدًا فقط يمكنه تعديله. @MainActor هو ممثل عام خاص يكون منفذه هو الخيط الرئيسي. أي كود مُوسم بـ @MainActor يُنفذ على الخيط الرئيسي — حتى لو تم استدعاؤه من مهمة خلفية.

قبل @MainActor، كان المطورون يتحولون يدويًا إلى الخيط الرئيسي عبر DispatchQueue.main.async. كان هذا مصدرًا متكررًا للأخطاء: كان المطورون ينسون التبديل، مما يؤدي إلى أعطال بسبب تحديث واجهة المستخدم ليس على الخيط الرئيسي. @MainActor يحل هذه المشكلة على مستوى نظام الأنواع.

أسباب الإنشاء

مصدر معظم الأخطاء في تطبيقات iOS هو عدم أمان واجهة المستخدم — تحديث الواجهة من خيط خلفي. قامت Apple بدمج @MainActor في Swift Concurrency لجعل التبديل إلى الخيط الرئيسي تلقائيًا وقابلًا للتحقق من قبل المترجم، مما يلغي فئة كاملة من أخطاء وقت التشغيل.

كيف يعمل @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 وتستدعي دالة غير متزامنة أخرى، فإنها ترث سياق الممثل افتراضيًا. هذا يعني أن جميع الاستدعاءات المتداخلة تُنفذ أيضًا على الخيط الرئيسي، ما لم يُحدد خلاف ذلك. يتتبع المترجم هذا ويصدر خطأ عند محاولة تمرير إغلاق غير متسق.

@MainActor مقابل DispatchQueue.main

مقارنة @MainActor و DispatchQueue.main تساعد في فهم لماذا تعتبر الآلية الجديدة أكثر أمانًا وملاءمة، على الرغم من أن كلاهما يحل نفس المهمة — تنفيذ الكود على الخيط الرئيسي.

الأمان على مستوى الأنواع

@MainActor هو تحقق على مستوى المترجم. إذا حاولت استدعاء دالة @MainActor من سياق غير آمن، سيصدر المترجم تحذيرًا أو خطأ. DispatchQueue.main.async هو استدعاء في وقت التشغيل: سيتجمّع الكود لكنه قد يتعطل في وقت التشغيل عند محاولة تحديث واجهة المستخدم من خيط خلفي.

الأداء والعبء

DispatchQueue.main.async يضيف كتلة إلى قائمة الانتظار قد يتم تنفيذها مع تأخير. @MainActor مع async/await يقوم بالتبديل المباشر للمنفذ دون إنشاء إغلاقات غير ضرورية. هذا يقلل من العبء ويجعل وقت التنفيذ أكثر قابلية للتنبؤ.

swift
// النهج القديم
DispatchQueue.main.async {
    self.updateUI()
}

// النهج الجديد مع @MainActor
@MainActor
func updateUI() {
    // يُتنفذ على الخيط الرئيسي
    self.label.text = "تم التحديث"
}
المعيار@MainActorDispatchQueue.main
التحققمترجموقت التشغيل
الصيغةوسم (تصريحي)استدعاء (أمرّي)
العبءمنخفض (تبديل منفذ)متوسط (إغلاق + قائمة)
قابلية الاختبارعالية (يمكن استبدال 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
    }
}

تغليف الكود القديم

عند العمل مع كود UIKit قديم حيث كان التبديل بين الخيوط يدويًا، يمكنك استخدام MainActor.run للتبديل الصريح. هذا مناسب للانتقال التدريجي إلى Swift Concurrency دون إعادة كتابة قاعدة الكود بأكملها.

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

قيود @MainActor

على الرغم من كل مزاياها، فإن @MainActor لديه عدد من القيود التي من المهم أخذها في الاعتبار عند تصميم بنية التطبيق. فهم حدود قابلية التطبيق يساعد في تجنب الاستخدام غير الصحيح.

الأداء مع الاستخدام المكثف

إذا كانت سلسلة الاستدعاءات بأكملها موسومة بـ @MainActor، فإن أي عمل ثقيل سيتم تنفيذه على الخيط الرئيسي، مما يسبب تجميد واجهة المستخدم. يُوصى بوسم طبقة واجهة المستخدم فقط بـ @MainActor، مع ترك منطق الأعمال وطلبات الشبكة في ممثلين خلفيين أو المنفذ العام.

عدم التوافق مع بعض APIs

واجهات البرمجة القديمة القائمة على الاستدعاءات (مثل URLSession بدون async/await) لا تدعم سياق الممثل. يتطلب التكامل غلافًا مع CheckedContinuation. أيضًا، @MainActor غير متوافق مع performSelector و target-action وأنماط UIKit غير المتزامنة الأخرى.

تصحيح الأخطاء متعددة الخيوط

عند تصحيح تطبيقات @MainActor، يصعب إعادة إنتاج ظروف السباق لأن المترجم يمنع العديد منها في وقت البناء بدلاً من وقت التشغيل. ومع ذلك، قد يخلق هذا شعورًا زائفًا بالأمان: العمل غير الصحيح مع الكائنات القابلة للتغيير المشتركة (مثل NSCache أو المتغيرات العالمية المشتركة) لا يزال ممكنًا إذا لم تكن موسومة بـ @MainActor وتُستخدم دون مزامنة صريحة.

اختبار @MainActor

@MainActor يبسط بشكل كبير اختبار منطق واجهة المستخدم حيث يلغي الحاجة إلى التبديل اليدوي للخيوط في الاختبارات. ومع ذلك، هناك خصوصيات يجب مراعاتها عند كتابة اختبارات الوحدة واختبارات واجهة المستخدم.

اختبارات الوحدة مع MainActor

في XCTest، تقوم بيئة الاختبار تلقائيًا بإعداد منفذ الخيط الرئيسي. عندما يتم تشغيل طريقة اختبار على الخيط الرئيسي، لا يتطلب استدعاء دوال @MainActor إعدادًا إضافيًا — يتم تنفيذها في نفس السياق. لاختبار السيناريوهات الخلفية، استخدم MainActor.run داخل Task مع أولوية ومنفذ صريحين، مع التحقق بشكل منفصل من أن الكود يعمل بشكل صحيح عند استدعائه من الخلفية.

أحد الأساليب الشائعة هو اختبار ViewModel مع @MainActor، حيث يتم التحقق من تحديث خصائص @Published بشكل صحيح بعد العمليات غير المتزامنة. بفضل توريث سياق الممثل، فإن استدعاء await داخل الاختبار يضمن التنفيذ على الخيط الرئيسي بدون ضمانات إضافية من DispatchQueue أو تبديل سياق يدوي، مما يبسط كتابة الاختبارات.

التحقق من العزل أثناء إعادة الهيكلة

عند إعادة هيكلة كود موجود إلى Swift Concurrency، تحقق من عزل @MainActor من خلال المترجم: أي استدعاء لطرق متزامنة بدون @MainActor من سياق @MainActor يُوسم كخطأ. تُستخدم هذه الخاصية للترحيل التدريجي لمشروع إلى async/await: تضع علامة على طبقة ViewModel كـ @MainActor، ويقوم المترجم بتسليط الضوء على جميع الاستدعاءات غير الآمنة التي يجب نقلها إلى ممثلين خلفيين.

المحاكاة وسياق الممثل

عند إنشاء محاكيات لتبعيات @MainActor، استخدم بروتوكولات بطرق غير متزامنة تعلن عن دوال غير متزامنة مع أنواع الإرجاع. هذا يسمح باستبدال خدمات الشبكة وقواعد البيانات والتبعيات الخارجية الأخرى دون كسر عزل الممثل. يتحقق المترجم من أن المحاكي ينفذ جميع متطلبات العزل، مما يمنع الوصول العرضي إلى كود @MainActor من خيوط اختبار خلفية.

الانتظار للعمليات غير المتزامنة

عند اختبار كود @MainActor بشكل متزامن، استخدم XCTestExpectation لانتظار اكتمال العمليات غير المتزامنة. قم بتعيين التوقع في الاختبار ونفذ fulfillment داخل إغلاق يتم تنفيذه على الخيط الرئيسي. إذا توقف الاختبار إلى أجل غير مسمى — فمن المحتمل أن الاستدعاء على الخيط الرئيسي لا يحدث، وتحتاج إلى التحقق من عزل الممثل. لتصحيح سياق التنفيذ، من المفيد إضافة تحقق Thread.isMainThread داخل كود الاختبار.

الأسئلة الشائعة

هل من الضروري وضع علامة على الفئة بأكملها بـ @MainActor؟

لا، يكفي وضع علامة فقط على الطرق التي تحدث واجهة المستخدم. ومع ذلك، إذا كانت الفئة تحتوي على عدة طرق من هذا القبيل، فمن الأسهل إضافة @MainActor إلى الفئة بأكملها. هذا يضمن أن جميع أعضائها يُنفذون على الخيط الرئيسي ويبسط صيانة الكود.

كيف يختلف @MainActor عن @globalActor؟

@MainActor هو مثيل محدد لممثل عام مرتبط بالخيط الرئيسي. @globalActor هو بروتوكول لإنشاء ممثلين عامين خاصين بك. على سبيل المثال، يمكنك إنشاء @BackgroundActor لتنفيذ كود على خيط خلفي إذا كانت بنية المشروع تتطلب ذلك.

هل يمكن استخدام @MainActor بدون async/await؟

نعم، الدوال المتزامنة مع @MainActor تُنفذ أيضًا على الخيط الرئيسي. ومع ذلك، تتجلى القيمة الرئيسية لـ @MainActor مع async/await، عندما تستأنف دالة غير متزامنة تلقائيًا على الخيط الرئيسي دون تبديل يدوي عبر DispatchQueue.main.

كيفية إلغاء مهمة @MainActor؟

Task.cancel() يعمل مع مهام @MainActor بنفس الطريقة كما هو الحال مع المهام العادية. يمكن لمهمة @MainActor التحقق من Task.isCancelled أو رمي CancellationError. عند الإلغاء، لا يتم حظر الخيط الرئيسي — تتوقف المهمة ببساطة عن التنفيذ عند أقرب نقطة تعليق.

ماذا يحدث إذا تم استدعاء @MainActor من خيط خلفي؟

يضمن المترجم الأمان: إذا استدعيت دالة @MainActor من سياق خلفي، سيشير المترجم إلى الخطأ. للاستدعاءات غير المتزامنة، يكفي وضع علامة await على الكود المستدعي، وسيقوم المنفذ نفسه بالتبديل إلى الخيط الرئيسي. للاستدعاءات المتزامنة، يلزم التبديل الصريح عبر MainActor.run.

الملخص

  • @MainActor — ممثل عام في Swift يضمن التنفيذ على الخيط الرئيسي.
  • التحقق من المترجم يلغي فئة كاملة من أخطاء أمان واجهة المستخدم.
  • وسم الفئة بالكامل يضع تلقائيًا جميع طرقها على الخيط الرئيسي.
  • MainActor.run — تبديل صريح للكود القديم والسياقات المتزامنة.
  • على عكس DispatchQueue.main، @MainActor لا يُنشئ إغلاقات ويستخدم تبديل المنفذ.
  • الحسابات الثقيلة لا يجب أن تُنفذ تحت @MainActor لتجنب تجميد واجهة المستخدم.
  • توريث سياق الممثل يبسط سلاسل الاستدعاءات غير المتزامنة ويجعل الكود متسقًا وقابلًا للتنبؤ وآمنًا لواجهة المستخدم.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا