التزامن في Swift: الخيوط، المهام الخفيفة و async/await — كيف لا تفقد صوابك

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا لا يتجمد هاتف iPhone الخاص بك أثناء تمرير الشاشة؟

\n\n

هل تساءلت يومًا لماذا لا تتباطأ تطبيقات iOS الحديثة، حتى عندما تقوم بتحميل عشرات الصور في وقت واحد، ومعالجة النقرات وكتابة البيانات في قاعدة البيانات؟ الإجابة بسيطة — التزامن. في عالم Swift (وليس فقط) هذا هو أحد أكثر المواضيع سخونة. وإذا كنت تكتب بلغة Swift ولكنك لا تزال مرتبكًا بين الخيوط والمهام الخفيفة و async/await الجديدة — مرحبًا بك في النادي. اليوم سنقوم بترتيب كل شيء، ليجعل كودك يطير بدلاً من أن يزحف.

\n\n

في هذه المقالة، لن نقوم فقط بسرد التعريفات الجافة. سننظر إلى أمثلة حقيقية، ونقارن بين الأساليب ونجيب على السؤال الرئيسي: ماذا تختار لمشروعك — GCD القديم الجيد أم Swift Concurrency الجديد؟

\n\n\n\n

الخيوط: الأساس الذي يقوم عليه كل شيء

\n\n

لنبدأ من الأساسيات. الخيط (Thread) هو أصغر وحدة معالجة يمكن لنظام التشغيل تنفيذها. تخيل خط إنتاج: خيط واحد هو خط واحد تتحرك عليه المهام. إذا كان لديك نواة معالج واحدة، فسيتم تنفيذ الخيوط بالتناوب، ولكن مع التبديل السريع جدًا، مما يخلق وهم التوازي. على المعالجات متعددة النوى، يمكن للخيوط أن تعمل بالتوازي فعليًا.

\n\n

في Swift، كانت الأداة الرئيسية للعمل مع الخيوط لفترة طويلة هي Grand Central Dispatch (GCD). كنت ببساطة تنشئ قائمة انتظار وترسل إليها إغلاقًا:

\n\n
DispatchQueue.global(qos: .background).async {\n    // عمل ثقيل في الخلفية\n    let result = heavyComputation()\n    DispatchQueue.main.async {\n        // تحديث واجهة المستخدم على الخيط الرئيسي\n        self.label.text = result\n    }\n}
\n\n

كل شيء بسيط، أليس كذلك؟ لكن GCD له تفاصيل دقيقة:

\n
    \n
  • أنت مسؤول عن العودة إلى الخيط الرئيسي (من السهل نسيان ذلك والحصول على تعطل).
  • \n
  • لا يوجد دعم مدمج لإلغاء المهام (يجب كتابة علامات يدويًا).
  • \n
  • من الصعب معالجة سلاسل العمليات التابعة دون تداخل الإغلاقات (ما يسمى بـ "callback hell").
  • \n
\n\n

ومع ذلك، لا يزال GCD حيًا ويُستخدم في المشاريع القديمة. لكن Swift الحديث يقدم شيئًا أكثر أناقة بكثير.

\n\n

المهام الخفيفة في Swift؟ لا، هذا ليس Go

\n\n

عند الحديث عن المهام الخفيفة (goroutines)، عادةً ما يتذكر المرء Go. في Swift لا يوجد نظير مباشر للمهام الخفيفة، ولكن هناك مهام خفيفة الوزن (tasks) تتصرف بشكل مشابه جدًا. إنها غير مرتبطة بخيط معين — النظام نفسه يقرر على أي خيط ينفذ المهمة، ويمكنه تبديلها بين الخيوط دون إنشاء خيوط جديدة لكل استدعاء.

\n\n

على عكس الخيوط التي "تزن" حوالي 8 كيلوبايت من المكدس لكل منها، فإن مهام Swift Concurrency لا تزن شيئًا تقريبًا. يمكنك تشغيل آلاف المهام دون خطر تجاوز الذاكرة. هذا هو "التزامن بدون ألم".

\n\n

انظر كيف يبدو هذا:

\n\n
Task {\n    let image = await downloadImage(from: url)\n    await MainActor.run {\n        self.imageView.image = image\n    }\n}
\n\n

هل لاحظت؟ لا يوجد DispatchQueue.main.async. فقط await MainActor.run. نظيف، مفهوم، آمن.

\n\n

async/await: الثورة التي حدثت

\n\n

مع إصدار Swift 5.5، ظهر دعم مدمج لـ async/await. هذا ليس مجرد سكر نحوي — إنه تغيير في النموذج. الآن يمكنك كتابة كود غير متزامن كما لو كان متزامنًا، ولكن دون حظر الخيط.

\n\n

الإعلان عن دالة غير متزامنة بسيط:

\n\n
func fetchUserData() async throws -> User {\n    let url = URL(string: "https://api.example.com/user")!\n    let (data, _) = try await URLSession.shared.data(from: url)\n    return try JSONDecoder().decode(User.self, from: data)\n}
\n\n

واستدعاؤها أسهل:

\n\n
Task {\n    do {\n        let user = try await fetchUserData()\n        print("User nam                

المدونات

توصيات الكتب