Back home

سلسلة Swift Concurrency 01|السبب وراء تقديم Swift غير المتزامن/الانتظار

إنها محاولة Swift لترقية التزامن من "الاعتماد على الاتفاقية" إلى "القدرة اللغوية"

عندما ترى async/await لأول مرة، ستفهمه على أنه “نسخة مطورة من كتابة رد الاتصال”.

وهذا الفهم نصف صحيح فقط.

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

في الماضي، ربما تحتاج فقط إلى معالجة نقرة زر لإرسال طلب، ولكن الآن قد تتضمن الصفحة العادية ما يلي:

  • تحميل موارد متعددة بالتوازي على الشاشة الأولى
  • إلغاء المهام القديمة عند مغادرة الصفحة
  • تتنافس ذاكرة التخزين المؤقت المحلية والطلب البعيد على نفس الحالة
  • تحديث الرمز المميز تلقائيًا بعد انتهاء صلاحية حالة تسجيل الدخول
  • تحدث تحديثات واجهة المستخدم الرئيسية ومعالجة الخلفية بشكل متقطع

بمجرد وصول الأعمال إلى هذا التعقيد، لم تعد المشكلة غير المتزامنة مجرد مشكلة في الكتابة، بل مشكلة في تصميم النظام. تكمن أهمية async/await على وجه التحديد في أنها تطور هذا النوع من المشكلات من “العادات على مستوى المكتبة” إلى “القواعد على مستوى اللغة”.

1. المشكلة الحقيقية في النموذج القديم هي أن تدفق التحكم مكسور

يحب الجميع استخدام “جحيم رد الاتصال” لشرح مشاكل طريقة الكتابة غير المتزامنة القديمة، ولكن إذا توقفت عند “المسافة البادئة عميقة جدًا”، فستظل تفوتك هذه النقطة.

المشكلة الحقيقية في عمليات الاسترجاعات هي أن العمل ** هو في الأصل سطر، ولكن يتم تقسيم الكود إلى العديد من الأجزاء المنفصلة. **

على سبيل المثال، عملية تهيئة شائعة جدًا:

  1. سحب معلومات المستخدم الحالية
  2. تحديد وحدة الصفحة الرئيسية بناءً على أذونات المستخدم
  3. اسحب بيانات الصفحة الرئيسية مرة أخرى
  4. في حالة الفشل، قم بتسجيل النقاط المخفية
  5. أخيرًا قم بالعودة إلى الموضوع الرئيسي لتحديث واجهة المستخدم

هذا رابط متسلسل بشكل واضح جدًا في ذهني. لكن في عصر الإكمال، غالبًا ما يتم تقسيم هذا الرابط إلى:

  • إغلاق الطلب
  • إغلاق حكم الإذن
  • عدة طبقات من معالجة الأخطاء
  • مفتاح الخيط الرئيسي
  • عدة فروع للعودة المبكرة

عندما يصبح الكود أطول، يصبح من الصعب شرحه بنظرة واحدة:

  • ما هي العملية الرئيسية؟
  • أي الفروع سوف تنكسر
  • من يجب أن يتحمل اللوم عندما تفشل الخطوة؟
  • هل يجب الاستمرار بعد مغادرة الصفحة؟

تكمن الصعوبة الحقيقية في الحفاظ على المنطق غير المتزامن في أن النية والتعبير بعيدان عن الواقع.

2. أكبر مشكلة في الإكمال هي أنه يترك الكثير من الدلالات الأساسية للاتفاقية

الإكمال ليس بالأمر السيئ. لا تزال العديد من واجهات برمجة التطبيقات الأساسية مفيدة جدًا اليوم، ولا تزال مناسبة في سيناريوهات معينة تتطلب عمليات رد اتصال متعددة، ومخرجات متدفقة، وسد الأنظمة القديمة.

المشكلة هي أنه عندما يعتمد النظام بشكل كبير على الاكتمال، فإن الكثير من الدلالات المهمة هي مجرد “عادات الفريق” وليست قواعد اللغة.

على سبيل المثال، غالبًا ما يتم فهم الأشياء التالية بشكل افتراضي في نموذج الإكمال:

  • هل سيتم استدعاء رد الاتصال هذا مرة واحدة أم عدة مرات؟
  • هل النجاح والفشل يتعارضان بشكل صارم؟
  • في أي موضوع سيعود رد الاتصال؟
  • ما إذا كان المتصل لديه القدرة على الإلغاء
  • إذا تم إطلاق الكائن في منتصف الطريق، فهل يجب الاستمرار في تسليم النتائج؟

ستجد أن هذه هي المشكلات الأساسية للأنظمة المتزامنة.

بمجرد الحفاظ على هذه الدلالات من خلال التوثيق والتسمية والخبرة، كلما كان المشروع أكبر، كان من الأسهل على الدلالات أن تتباعد. الشيء الأكثر إيلامًا في النهاية هو عادةً عدم القدرة على التفكير بشكل ثابت حول كيفية تشغيل جزء من التعليمات البرمجية غير المتزامنة.

3. يقدم Swift خاصية async/await، وهو ما يؤدي بشكل أساسي إلى تشديد قواعد التعليمات البرمجية غير المتزامنة.

لا تقتصر قيمة async/await على ما يلي:

fetchUser { result in
  ...
}

استبدل بـ:

let user = try await fetchUser()

والأهم من ذلك أنه يعيد العديد من الأشياء التي كانت متناثرة في الأصل في الاتفاقية إلى مستوى اللغة.

على سبيل المثال الآن:

  • تتمتع الدالة async بنقطة إرجاع واضحة
  • يتم نشر الفشل عبر throw بدلاً من نمط مختلف لاستدعاء النتيجة
  • يشير await بوضوح إلى أن هذه هي نقطة الإيقاف المؤقت
  • يمكن قراءة سلسلة المكالمات غير المتزامنة دون القفز بين عمليات الإغلاق المتعددة.

أهمية هذا الأمر ليست “المظهر الجيد” بل “القواعد أكثر صرامة”.

الخوف الحقيقي من الأنظمة المعقدة هو دائمًا القواعد الفضفاضة. بمجرد تخفيف القواعد، يصبح الكود معتمدًا بشكل متزايد على “هذا الشخص يفهم السياق”.

4. إنه لا يحسن سهولة القراءة فحسب، بل أيضًا المعقولية

الموقف الشائع هو: يمكنني فهم الاكتمال، لماذا يجب أن أتعلم async/await؟

السؤال ليس ما إذا كان بإمكانك فهم ذلك اليوم، ولكن:

  • هل لا يزال بإمكانك الفهم بسرعة بعد ثلاثة أشهر؟
  • عندما يتولى الزملاء المهمة، هل يمكنهم استنتاج العملية بناءً على الكود نفسه؟
  • عند حدوث خلل هل من الممكن السير من المدخل إلى المخرج؟

هذا هو الفرق بين “سهولة القراءة” و “المعقولية”.

سهولة القراءة هي “هل هذا الكود يرضي عيني اليوم؟” المعقولية هي “هل يمكنني الحكم بناءً على هذا الكود: كيف يتم نقل الفشل، وكيف يتم تفعيل الإلغاء، وعلى أي مستوى يتم تغيير الحالة.”

على سبيل المثال، غالبًا ما يكون من الصعب جدًا متابعة المشكلات التالية في النموذج القديم:

  • بعد مغادرة المستخدم للصفحة، هل تظل سلسلة الطلب هذه موجودة؟
  • عند فشل الخطوة الثالثة، أين ستتوقف حالة الصفحة النهائية؟
  • عندما تعود نتيجتان غير متزامنتين في نفس الوقت، من هو المؤهل لكتابة واجهة المستخدم؟

من المؤكد أن async/await لن يجيب على هذه الأسئلة تلقائيًا للفريق، ولكنه على الأقل يسمح بوضع هذه الأسئلة في بنية عملية أكثر وضوحًا بدلاً من إخفائها في عمليات إغلاق مجزأة.

5. أصبحت هذه المسألة أكثر أهمية في مشاريع iOS

أحد أكبر التغييرات في مشروع iOS اليوم هو أن “عدم التزامن قد تغير من قدرة هامشية إلى قدرة أساسية”.

في الماضي، كان الأمر غير المتزامن يدور حول “النقر وإرسال طلب”. الآن يشارك بشكل غير متزامن بشكل مباشر في دورة حياة الصفحة، وجهاز الحالة، وطبقة ذاكرة التخزين المؤقت، ونظام الأذونات، ونظام النقاط المخفية، بل ويشارك في تصميم سرعة الاستجابة للمنتج بأكمله.

الموقف الشائع لصفحة الأعمال الحقيقية هو:

  • سيكون هناك طلب على الشاشة الأولى بمجرد فتح الصفحة.
  • تستخدم بعض الوحدات ذاكرة التخزين المؤقت المحلية للتهرب
  • تعتمد بعض الطلبات على ملفات تعريف المستخدمين أو التكوينات التجريبية
  • تتطلب بعض العمليات منع إعادة الدخول
  • يجب إرجاع بعض النتائج إلى الموضوع الرئيسي لتغيير الحالة بأمان

في ظل هذه الفرضية، إذا كان النظام غير المتزامن لا يزال مجرد “كل شخص يكتب إكماله الخاص”، فسوف ينزلق الكود قريبًا من “يمكن أن يعمل” إلى “لا أحد يجرؤ على التغيير”.

إذن ما يلفت انتباه async/await حقًا هو أن أسلوب العمل غير المتزامن أصبح طريقة العمل الافتراضية في التطبيقات الحديثة.

6. إنه المدخل إلى نموذج التزامن الكامل لـ Swift.

إذا نظرت فقط إلى هذه الميزة النحوية، يبدو أن async/await مجرد تعبير غير متزامن أفضل.

ولكن إذا نظرت إلى Swift Concurrency ككل، فستجد أنها في الواقع تمهد الطريق لإمكانيات لاحقة:

  • Task: توضيح حدود المهام ودورة الحياة
  • MainActor: تقييد دلالات التنفيذ للحالات ذات الصلة بواجهة المستخدم
  • Actor: عزل الحالة المشتركة القابلة للتغيير
  • التزامن المنظم: دمج المهام الفرعية في دورة حياة المهمة الرئيسية

بمعنى آخر، لا يحاول Swift فقط توفير واجهة برمجة تطبيقات غير متزامنة أكثر ملاءمة، بل يحاول تحويل “التزامن” من تقنية مجزأة إلى مجموعة من النماذج القابلة للتركيب والمعقولة والقابلة للتحقق من اللغة.

يوضح هذا أيضًا أنه لا يمكن اعتبار async/await مجرد “أسهل في الكتابة”. ما يرتبط حقًا وراء ذلك هو مجموعة جديدة تمامًا من فلسفات التصميم المتزامن.

7. لا يحل جميع المشاكل تلقائياً، لكنه يغير طبيعة المشكلة.

ويجب أيضًا توضيح ذلك هنا: مع async/await، لن تختفي أخطاء التزامن تلقائيًا.

لا يحل:

  • التصميم الخاطئ لحدود الدولة
  • المهام القديمة تحل محل النتائج الجديدة
  • يتحمل ViewModel الكثير من المسؤوليات في نفس الوقت
  • لم يتم التفكير في استراتيجية إلغاء المهمة على الإطلاق

لكنه يغير شيئا مهما جدا: لم تعد العديد من المشكلات “لأن التعبير مربك للغاية ولا أستطيع حتى رؤية شكل المشكلة”، ولكن “تم التعبير عن المشكلة بوضوح”.

قد يبدو هذا بمثابة خطوة إلى الوراء، لكنه في الواقع خطوة كبيرة إلى الأمام. لأنه فقط عندما تصبح المشكلة واضحة، يمكن أن يكون تصحيح الأخطاء والمراجعة وإعادة البناء فعالاً.

8. الخلاصة: يقدم Swift خاصية المزامنة/الانتظار حتى لا يتم كتابة عدد أقل من عمليات الإغلاق

ولكي أختصر الأمر أقول:

تقدم Swift async/await لجعل التعليمات البرمجية غير المتزامنة مفهومة ومعقولة وقابلة للصيانة مرة أخرى.

لذا فهذه هي الخطوة الأولى في ترقية نموذج التزامن.

بمجرد أن تفهم ذلك، عندما تنظر إلى Task وActor وMainActor لاحقًا، فلن تعتبرها نقاط بناء جملة متناثرة، لكنك ستفهم أن ما يفعله Swift هو شيء أكبر: **إعادة تصنيف التزامن من “المهارات التجريبية” إلى “قواعد اللغة”. **

FAQ

What to read next

Related

Continue reading