قوائم الانتظار والمهام الخلفية في Swift: كيف تجعل التطبيق يطير
\n\nتخيل: تفتح تطبيقًا لطلب البيتزا، تضغط "عرض القائمة"، وتتجمد الشاشة لمدة خمس ثوانٍ. تبدأ بالتوتر، تبدو البيتزا غير لذيذة، ويطير التطبيق إلى سلة المهملات. ألم مألوف؟ إذا كنت مطورًا في Swift، فبالتأكيد واجهت موقفًا حيث "يتباطأ" الواجهة بسبب العمليات الثقيلة - تحميل الصور، تحليل JSON أو الحسابات المعقدة. سر تجربة المستخدم السلسة يكمن في العمل الصحيح مع قوائم الانتظار والمهام الخلفية.
\n\nفي هذه المقالة، سنشرح كيفية تنظيم تعدد المهام في Swift: من GCD الكلاسيكي و OperationQueue إلى async/await الحديث. ستتعلم كيفية تفريغ العمل الثقيل من الخيط الرئيسي، وتجنب حالات الجمود (deadlocks)، وكتابة كود يعمل بسرعة وبشكل متوقع. هيا بنا!
لماذا قائمة الانتظار ليست عن الملل في المتجر
\n\nفي Swift، قائمة الانتظار ليست المكان الذي تقف فيه مع عربة التسوق. قوائم الانتظار (DispatchQueue) هي آلية لإدارة المهام. تخيل أن لديك عدة طهاة (خيوط). طاهٍ واحد يخدم الصالة (الخيط الرئيسي - واجهة المستخدم)، والباقي يطبخون في المطبخ (الخيوط الخلفية). إذا جعلت النادل يشوي شريحة لحم، ستتوقف الصالة. نفس الشيء مع الواجهة: جميع تحديثات واجهة المستخدم يجب أن تحدث فقط على الخيط الرئيسي، وكل شيء آخر في الخلفية.
\n\nفي Swift، هناك نوعان من قوائم الانتظار:
\n- \n
- Serial (متسلسلة) - يتم تنفيذ المهام واحدة تلو الأخرى. مثالية لحماية الموارد المشتركة. \n
- Concurrent (متزامنة) - تبدأ المهام في وقت واحد. ممتازة لتحميل الصور أو الحسابات المتوازية. \n
النظام يدير بنفسه مجموعة الخيوط، لذلك لا تحتاج إلى إنشائها يدويًا. فقط قل: "افعل هذا في الخلفية، ثم أعد النتيجة إلى الخيط الرئيسي". و Swift يفعل ذلك بأناقة.
\n\nGCD (Grand Central Dispatch) - الصديق القديم أفضل من الجديدين
\n\nGrand Central Dispatch هو API منخفض المستوى ظهر في Objective-C. لا يزال حيًا وبصحة جيدة، ويجب على كل مطور Swift معرفته. العمل الأساسي يتم من خلال DispatchQueue.
إليك أبسط مثال - تحميل البيانات في الخلفية وتحديث واجهة المستخدم:
\n\nimport UIKit\n\nclass ViewController: UIViewController {\n @IBOutlet weak var imageView: UIImageView!\n \n override func viewDidLoad() {\n super.viewDidLoad()\n loadImage()\n }\n \n func loadImage() {\n // نذهب إلى قائمة انتظار خلفية\n DispatchQueue.global(qos: .background).async {\n // عمل ثقيل: تحميل الصورة\n guard let url = URL(string: "https://example.com/pizza.jpg"),\n let data = try? Data(contentsOf: url),\n let image = UIImage(data: data) else {\n return\n }\n \n // نعود إلى الخيط الرئيسي لتحديث واجهة المستخدم\n DispatchQueue.main.async {\n self.imageView.image = image\n }\n }\n }\n}\n\n\nلاحظ qos (Quality of Service). هذا هو أولوية المهمة: .userInteractive - للرسوم المتحركة، .background - للمزامنة، .utility - لمؤشرات التقدم. الاختيار الصحيح لـ QoS يساعد النظام في توزيع الموارد.
DispatchGroup: ننتظر الجميع، وليس واحدًا
\n\nتخيل أنك بحاجة لتحميل ثلاث صور ثم عرضها فقط. بدون DispatchGroup، ستقوم بتشغيل ثلاثة طلبات غير متزامنة ولن تعرف متى تنتهي جميعها. الحل هو المجموعة:
func loadMultipleImages() {\n let group = DispatchGroup()\n var images: [UIImage] = []\n \n let urls = ["url1", "url2", "url3"]\n \n for urlString in urls {\n group.enter() // ندخل المجموعة\n DispatchQueue.global().async {\n // تحميل