مقدمة: لماذا "الانتظار" ليس بسيطًا كما يبدو
\n\nتخيل: أنت تكتب تطبيق iOS بلغة Swift 3. تحتاج إلى عرض حركة، وبعد بضع ثوانٍ - إخفائها. أو إرسال طلب إلى الخادم، ولكن ليس فورًا، بل مع تأخير. قد يبدو الأمر بسيطًا؟ تكتب sleep(2) - وتنتهي. لكن إذا فعلت ذلك، سيتحول تطبيقك إلى قطعة ثلج: ستتجمد الواجهة، وسيبدأ المستخدم في النقر على الشاشة، وفي النهاية - تقييم سلبي وفقدان التصنيف.
سؤال "How to program a delay in Swift 3" حصل على آلاف المشاهدات على Stack Overflow. وهذا ليس من قبيل الصدفة: المبتدئون يقعون باستمرار في نفس الأخطاء. في هذه المقالة، لن نقدم وصفة جاهزة فقط. سنتعمق تحت الغطاء لآلية التأخيرات، ونفهم كيف يعمل DispatchQueue.main.asyncAfter، ونعرف لماذا sleep هو شر. وسأريكم أيضًا خدعة ستوفر ساعات من التصحيح.
لماذا sleep() هو نمط مضاد في Swift
\n\nدعنا نوضح فورًا أكثر المفاهيم الخاطئة شيوعًا. كثيرون ممن يأتون من لغات أخرى (مثل Python مع time.sleep()) يحاولون فعل هذا:
sleep(2) // نمنع الخيط لمدة ثانيتين\nprint("مرت ثانيتان")\n\nهذا الكود يمنع الخيط الحالي. إذا استدعيته على الخيط الرئيسي (main thread) - انتهى الأمر، التطبيق "ينام" لمدة ثانيتين. واجهة المستخدم لا تتحدّث، الأزرار لا تضغط، الحركات لا تعمل. iOS سيقتل تطبيقك إذا تم حظر الخيط الرئيسي لفترة طويلة جدًا (عادة أكثر من 10 ثوانٍ).
\n\nبالإضافة إلى ذلك، sleep() لا يأخذ في الاعتبار حالة RunLoop. إنه استدعاء نظام خام لا يسمح للنظام بمعالجة الأحداث بشكل طبيعي. في Swift 3 (وما بعده) الطريق الصحيح هو استخدام Grand Central Dispatch (GCD).
DispatchQueue.main.asyncAfter: الطريقة الصحيحة لعمل تأخير
\n\nالطريقة الأكثر شيوعًا وموصى بها لتأخير تنفيذ الكود في Swift 3 هي طريقة asyncAfter من GCD. إليك كيف تبدو:
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {\n print("مرت ثانيتان (وضع غير مانع)")\n}\n\nهنا نقول للنظام: "بعد ثانيتين من اللحظة الحالية، نفذ هذه الكتلة من الكود على الخيط الرئيسي". الخيط الرئيسي يبقى حرًا - الحركات تعمل، الأزرار تضغط، المستخدم سعيد.
\n\nلكن دعنا نتعمق أكثر. ما هو deadline؟ لماذا .now() + 2.0 وليس فقط 2.0؟ وماذا يحدث "تحت الغطاء"؟
تشريح asyncAfter: deadline, DispatchTime و DispatchQoS
\n\nasyncAfter يقبل معامل deadline من نوع DispatchTime. هذا ليس مجرد عدد من الميلي ثانية، بل نقطة زمنية مطلقة مرتبطة بساعة النظام. عندما تكتب .now() + 2.0، يحسب GCD: "الوقت الحالي + ثانيتين" ويضع المهمة في قائمة الانتظار بهذه العلامة.
تحت الغطاء، يستخدم GCD mach_absolute_time() - مؤقت أجهزة لا يعتمد على تغييرات وقت النظام. أي إذا قام المستخدم بتقديم الساعة للخلف، لن يتأثر تأخيرك (على عكس Timer الذي قد يتصرف بشكل غير متوقع).
فارق مهم: deadline هو الحد الأدنى للوقت حتى التنفيذ. GCD لا يضمن أن الكود سينفذ بالضبط بعد ثانيتين. إذا كان النظام مشغولاً (مثل معالجة بيانات معقدة)، قد يتأخر التنفيذ. بالنسبة لمعظم مهام واجهة المستخدم، هذا ليس حرجًا، لكن للمؤقتات عالية الدقة (مثل الألعاب) من الأفضل استخدام CADisplayLink.
معامل آخر - qos (Quality of Service). يمكنك تحديد أولوية المهمة:
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1.0) {\n // مهمة ذات أولوية عالية\n}\n\n
المدونات
توصيات الكتب
اقرأ أيضًا