لماذا تعتبر الخيوط الخلفية كابوسًا لكل مطور iOS؟
\n\nتخيل: أنت تكتب تطبيقًا لتحميل الصور من الإنترنت. كل شيء يعمل بشكل مثالي... حتى يضغط المستخدم على زر "تحميل". تتجمد الشاشة، ويبطئ التطبيق، وبعد بضع ثوانٍ يقوم iOS بقتل عمليتك. هل هذا مألوف؟
\n\nالمشكلة هي أن جميع العمليات يتم تنفيذها افتراضيًا على الخيط الرئيسي (main thread). والخيط الرئيسي مسؤول عن عرض واجهة المستخدم (UI) - إذا قمت بحظره بمهمة طويلة، ستتوقف الواجهة عن الاستجابة للمسات. الحل؟ استخدام الخيط الخلفي (background thread) في Swift.
\n\nفي هذه المقالة، سنتعلم كيفية العمل مع الخيوط الخلفية من خلال أمثلة عملية. ستتعرف على GCD وOperationQueue و async/await الجديد من Swift 5.5+. هيا بنا!
\n\nما هو الخيط (thread) في Swift؟
\n\nالخيط هو عملية خفيفة تنفذ كود البرنامج. يحتوي تطبيق iOS على خيط رئيسي (main thread) والعديد من الخيوط الخلفية. الخيط الرئيسي يتعامل مع واجهة المستخدم: تحديث الشاشة، معالجة الإيماءات، الرسوم المتحركة. كل ما يتعلق بالواجهة يجب أن يحدث فقط على الخيط الرئيسي.
\n\nالخيوط الخلفية (background threads) ضرورية من أجل:
\n- \n
- تحميل البيانات من الشبكة \n
- العمل مع الملفات (قراءة/كتابة) \n
- الحسابات المعقدة (معالجة الصور، التشفير) \n
- العمل مع قواعد البيانات \n
إذا حاولت تحديث واجهة المستخدم من خيط خلفي - سيتعطل التطبيق مع خطأ، أو سترى سلوكًا غريبًا للواجهة.
\n\nGCD (Grand Central Dispatch) - الطريقة التقليدية
\n\nGrand Central Dispatch (GCD) هي مكتبة منخفضة المستوى لإدارة الخيوط، مدمجة في iOS/macOS. تعمل من خلال قوائم الانتظار (queues): تضع المهمة في قائمة الانتظار، ويقرر النظام بنفسه على أي خيط ينفذها.
\n\nDispatchQueue.main - الخيط الرئيسي
\nإذا كنت بحاجة لتنفيذ كود على الخيط الرئيسي (على سبيل المثال، تحديث UILabel)، استخدم:
\n\nDispatchQueue.main.async {\n self.label.text = "تم التحميل!"\n}\n\nDispatchQueue.global() - الخيط الخلفي
\nللمهام الخلفية، استخدم قائمة الانتظار العامة:
\n\nDispatchQueue.global(qos: .background).async {\n // عمل ثقيل\n let imageData = try? Data(contentsOf: url)\n \n // نعود إلى الخيط الرئيسي لواجهة المستخدم\n DispatchQueue.main.async {\n self.imageView.image = UIImage(data: imageData!)\n }\n}\n\nالمعامل qos (جودة الخدمة) يحدد أولوية المهمة:
- \n
.userInteractive- الأعلى، لواجهة المستخدم \n .userInitiated- المستخدم ينتظر النتيجة \n .utility- مهام طويلة (تحميل الملفات) \n .background- أولوية منخفضة (مزامنة، نسخ احتياطي) \n
OperationQueue - تحكم أكبر
\n\nOperationQueue هي طبقة فوق GCD مع إمكانيات إضافية:
\n- \n
- إلغاء المهام \n
- التبعيات بين المهام \n
- الحد الأقصى لعدد الخيوط المتوازية \n
مثال لتحميل عدة صور:
\n\nlet queue = OperationQueue()\nqueue.maxConcurrentOperationCount = 2 // لا يزيد عن مهمتين في وقت واحد\n\nlet operation1 = BlockOperation {\n // تحميل الصورة الأولى\n sleep(2)\n print("تم تحميل الصورة 1")\n}\n\nlet operation2 = BlockOperation {\n // تحميل الصورة الثانية\n sleep(3)\n print("تم تحميل الصورة 2")\n}\n\n// operation2 ستبدأ فقط بعد انتهاء operation1\noperation2.addDependency(operation1)\n\nqueue.addOperations([operation1, operation2], waitUntilFinished: false)\n\nهذا مفيد عندما تحتاج لتنفيذ المهام بترتيب صارم أو مع قيود على الموارد.
\n\nasync/await الحديث (Swift 5.5+)
\n\nبدءًا من Swift 5.5، Ap