dispatch_after في Swift: كيف ينفذ GCD المهام المؤجلة تحت الغطاء
\n\nتخيل: أنت تكتب تطبيقًا بلغة Swift، وتحتاج إلى تنفيذ جزء من الكود ليس فورًا، بل، لنقل، بعد 3 ثوانٍ. أول ما يتبادر إلى الذهن هو dispatch_after. ولكن إذا ألقيت نظرة على الإصدارات القديمة من Swift أو على Stack Overflow، فسترى أن هذه الطريقة قد تغيرت. والأهم من ذلك، أن القليل من يفهم ما يحدث بالفعل داخل Grand Central Dispatch (GCD) عندما نقول: "افعل هذا بعد ثانيتين".
في هذه المقالة، لن نعرض فقط كيفية كتابة dispatch_after في Swift الحديث. بل سنتعمق تحت غطاء GCD، ونحلل الطوابير، والخيوط، والمؤقتات، لتتمكن من كتابة كود غير متزامن موثوق وفعال. هيا بنا!
ما هو dispatch_after ولماذا أصبح asyncAfter؟
\n\nفي الإصدارات القديمة من Swift (1.x، 2.x)، قد تصادف هذا الكود:
\n\ndispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2 * NSEC_PER_SEC)), dispatch_get_main_queue()) {\n print("مرت ثانيتان")\n}\n\nكان هذا الكود يعمل، لكنه يبدو ضخمًا. بدءًا من Swift 3، قامت Apple بإعادة هيكلة واجهة GCD API بأكملها، مما جعلها أكثر "سويفتية". الآن، نفس الوظيفة تبدو كالتالي:
\n\nDispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {\n print("مرت ثانيتان")\n}\n\nلاحظ: asyncAfter هي طريقة تابعة للطابور (DispatchQueue)، وليست دالة منفصلة. هذا تغيير جوهري: الآن أنت تحدد بوضوح أي طابور سينفذ المهمة.
كيف يعمل GCD تحت الغطاء: الطوابير، الخيوط، والمؤقتات
\n\nلفهم dispatch_after (أو asyncAfter)، تحتاج إلى فهم ثلاث كيانات:
- \n
- DispatchQueue — طابور المهام. يمكن أن يكون تسلسليًا (serial) أو متوازيًا (concurrent). \n
- DispatchWorkItem — المهمة التي تضعها في الطابور. في حالتنا، هذا هو الإغلاق (closure). \n
- DispatchTime — اللحظة الزمنية التي يجب أن تبدأ فيها المهمة. يستخدم GCD مؤقتًا نظاميًا لتتبع المواعيد النهائية. \n
عندما تستدعي asyncAfter، لا يقوم GCD بإنشاء خيط جديد. بدلاً من ذلك، يقوم بـ:
- \n
- إنشاء مؤقت (timer) سيعمل في الوقت المحدد. \n
- وضع المهمة في طابور خاص بالمهام المؤجلة (يُسمى غالبًا dispatch timer). \n
- عندما يعمل المؤقت، ينقل GCD المهمة إلى الطابور العادي
DispatchQueue، حيث تنتظر خيطها. \n
إذا كان الطابور الذي تضع فيه المهمة مشغولاً بمهام أخرى، فقد يتم تنفيذ مهمتك المؤجلة بعد الموعد النهائي المحدد. هذه نقطة مهمة: dispatch_after يضمن أن المهمة لن تُنفذ قبل الموعد النهائي، ولكن قد تُنفذ بعده.
مثال عملي: dispatch_after لتحديث واجهة المستخدم
\n\nالسيناريو الأكثر شيوعًا هو تحديث الواجهة بعد فترة زمنية من إجراء المستخدم. لنفترض أنك تعرض رسالة "تم الحفظ"، وبعد ثانيتين تقوم بإخفائها:
\n\nfunc showTemporaryMessage() {\n let messageLabel = UILabel()\n messageLabel.text = "تم الحفظ!"\n messageLabel.alpha = 1.0\n view.addSubview(messageLabel)\n\n DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {\n UIView.animate(withDuration: 0.5) {\n messageLabel.alpha = 0.0\n } completion: { _ in\n messageLabel.removeFromSuperview()\n }\n }\n}\n\nلاحظ: نحن نستخدم DispatchQueue.main لأن جميع العمليات المتعلقة بواجهة المستخدم يجب أن تُنفذ على الخيط الرئيسي. إذا استدعينا asyncAfter على طابور خلفي ثم حاولنا تغيير واجهة المستخدم، فسينهار التطبيق.
المطبات الخفية في dispatch_after: تسرب الذاكرة والاستيلاء على self
\n\nأحد الأخطاء الأكثر شيوعًا هو المرجع القوي لـ self داخل