نادرًا ما يتم استخدام آلة الحالة من قبل مطوري الهواتف المحمولة. على الرغم من أن الأغلبية تعرف مبادئ العمل وتنفذها بسهولة بشكل مستقل. في المقالة ، سنكتشف المهام التي يتم حلها بواسطة جهاز الحالة باستخدام مثال تطبيقات iOS. يتم تطبيق القصة في الطبيعة ومخصصة للجوانب العملية للعمل.
تحت الخفض ، ستجد
نصًا موسعًا لخطاب ألكسندر سيشيف (
Brain89 ) على
AppsConf ، والذي شارك فيه خياراته لاستخدام جهاز الحالة في تطوير تطبيقات غير ألعاب.
نبذة عن المتحدث: شارك ألكسندر سيشيف في تطوير iOS لمدة ثماني سنوات ، شارك خلالها في إنشاء تطبيقات بسيطة وعملاء معقدين للشبكات الاجتماعية والقطاع المالي. في الوقت الحالي ، هو رائد فني في Sberbank.
يأتون إلى البرمجة من العديد من المجالات ، ولديهم خلفيات وخبرات مختلفة ، لذلك نتذكر أولاً النظرية الأساسية.
بيان المشكلة

آلة الحالة عبارة عن تجريد رياضي يتكون من ثلاثة عناصر رئيسية:
- العديد من الدول الداخلية
- مجموعة إشارات الإدخال التي تحدد الانتقال من الحالة الحالية إلى الحالة التالية ،
- مجموعات من الحالات النهائية ، عند الانتقال التي يكملها الأوتوماتي عمله ("تقبل كلمة الإدخال س").
الشرط
نعني بالحالة متغيرًا أو مجموعة من المتغيرات التي تحدد سلوك كائن ما. على سبيل المثال ، في تطبيق "الإعدادات
" القياسي لنظام iOS ، يوجد عنصر "غامق" ("أساسي → وصول عام"). تسمح لك قيمة هذا العنصر بالتبديل بين خيارين لعرض النص على شاشة الجهاز.

عن طريق إرسال نفس الإشارة "تغيير قيمة مفتاح التبديل
" ، نحصل على رد فعل مختلف للنظام: إما نمط الخط المعتاد أو غامق - كل شيء بسيط. كونه في حالات مختلفة ويتلقى نفس الإشارة ، يتفاعل الكائن بشكل مختلف مع التغيير في الحالة.
المهام التقليدية
من الناحية العملية ، غالبًا ما يصادف المبرمجون آلة محدودة.
تطبيقات الألعاب
هذا هو أول شيء يتبادر إلى الذهن - كجزء من اللعب ، يتم تحديد كل شيء تقريبًا من خلال حالة اللعبة الحالية. لذا ، تفترض Apple استخدام أجهزة الحالة بشكل أساسي في تطبيقات الألعاب (سنناقش هذا بالتفصيل لاحقًا).
يمكن توضيح سلوك النظام عند معالجة نفس الإشارة ولكن مع حالة داخلية مختلفة من خلال الأمثلة التالية. على سبيل المثال:
● يمكن أن تكون شخصية اللعبة ذات نقاط قوة مختلفة: واحدة في الدروع الميكانيكية وبندقية الليزر ، والأخرى يتم ضخها بشكل ضعيف. اعتمادًا على هذه الحالة ، يتم تحديد سلوك الأعداء: إما أن يهاجموا أو يهربوا.

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

رسميا ، هذه هي مهمة العثور على سلسلة فرعية في سلسلة. لحلها ، يتم استخدام خوارزمية Knut-Morris-Pratt ، والتي يكون تنفيذ البرنامج لها عبارة عن آلة حالة محدودة. الحالة هي إزاحة تسلسل الإدخال وعدد الأحرف الموجودة في النمط - كلمة التوقف.
أيضًا ،
عند تحليل التعبيرات العادية ، غالبًا ما يتم استخدام آلات الحالة المحدودة.
معالجة الاستعلام الموازي
جهاز الحالة هو أحد الخيارات لتنفيذ معالجة الطلب وتنفيذ مجموعة صارمة من التعليمات.

على سبيل المثال ، في خادم الويب nginx ، تتم معالجة طلبات إدخال البروتوكولات المختلفة باستخدام أجهزة الحالة. بناءً على البروتوكول المحدد ، يتم تحديد تنفيذ محدد لآلة الحالة ، وبالتالي يتم تنفيذ مجموعة من التعليمات المعروفة.
بشكل عام ، يتم الحصول على فئتين من المشاكل:
- إدارة منطق كائن معقد مع حالة داخلية معقدة ،
- تشكيل التحكم وتدفق البيانات (وصف الخوارزمية).
من الواضح أن مثل هذه المهام الشائعة تصادف في ممارسة أي مبرمج. لذلك ، من الممكن استخدام جهاز حالة ، بما في ذلك في تطبيقات المحتوى غير المخصصة للألعاب ، والتي تشارك في معظم مطوري الهواتف المحمولة.
بعد ذلك ، سنحلل أين ومتى يمكن استخدام آلة الحالة لإنشاء تطبيقات iOS نموذجية.
تحتوي معظم تطبيقات الهاتف المحمول على بنية ذات طبقات. هناك ثلاث طبقات أساسية.
- طبقة العرض.
- طبقة منطق الأعمال.
- مجموعة من المساعدين وعملاء الشبكة وما إلى ذلك (الطبقة الأساسية).
كما هو موضح أعلاه ، تتحكم آلة الحالة في الكائنات ذات السلوك المعقد ، أي مع حالة معقدة. توجد هذه الكائنات بالتأكيد في طبقة العرض التقديمي ، لأنها تتخذ قرارات عن طريق معالجة مدخلات المستخدم أو الرسائل من نظام التشغيل. دعونا نلقي نظرة على الأساليب المختلفة لتنفيذها.

في الاستعارة المعمارية الكلاسيكية لـ Model-View-Controller ، ستكون الحالة في وحدة التحكم: فهي تقرر ما يتم عرضه في طريقة العرض وكيفية الاستجابة لإشارات الإدخال: الضغط على زر وتغيير شريط التمرير وما إلى ذلك. من المنطقي أن أحد تطبيقات وحدة التحكم هو جهاز الحالة.

في VIPER ، تكون الدولة في مقدم العرض: هو الذي يحدد انتقال التنقل المحدد من الشاشة الحالية وعرض البيانات في طريقة العرض.

في Model-View-ViewModel ، تكون الحالة في ViewModel. بغض النظر عما إذا كان لدينا ملفات تفاعلية أم لا ، سيتم تسجيل سلوك الوحدة المحددة في استعارة MVVM في ViewModel. من الواضح أن تنفيذه من خلال جهاز الدولة هو خيار مقبول.

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

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

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

يمكن إرسال بيانات حقيقية أو خطأ إلى الشاشة. لذلك يظهر أول عامل شرطي ، الفرع الأول ، الذي يحدد السلوك الإضافي للتطبيق.
السؤال التالي هو ماذا تفعل إذا لم تكن هناك بيانات. هل هذا الشرط خطأ؟ على الأرجح لا: ليس لدى كل أخبار تعليقات المستخدمين. على سبيل المثال ، لا يهتم الهوكي في مصر بأي شخص ؛ في مثل هذه المقالة لا توجد تعليقات في العادة. هذا هو السلوك الطبيعي والحالة العادية للشاشة التي تحتاج إلى عرضها. لذلك يظهر العامل الشرطي الثاني.
من المنطقي أن نفترض أن هناك أيضًا حالة بداية يتوقع فيها المستخدم البيانات (على سبيل المثال ، عندما تظهر شاشة التعليق على الشاشة فقط). في هذه الحالة ، اعرض مؤشر التحميل بشكل صحيح. هذا هو البيان الشرطي الثالث.
لذلك لدينا بالفعل أربع حالات على شاشة واحدة بسيطة ، يتم وصف منطق العرض الخاص بها من خلال بنية if-else-if-else.

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

بادئ ذي بدء ، من
الصعب القراءة .
نظرًا لأن قراءة الرمز ضعيفة ، فهذا يعني أنه سيكون من الصعب على المطور الجديد معرفة ما يتم تنفيذه بالضبط في مكان معين في المشروع. وبناءً على ذلك ، سوف يقضي الكثير من الوقت في تحليل منطق سلوك التطبيق -
ستزداد تكلفة الدعم والتطوير .
هذا الرمز غير
مرن . إذا كنت بحاجة إلى إضافة حالة جديدة لا تتبع السلم الحالي ، فقد لا تنجح على الإطلاق! إذا كنت بحاجة إلى ممر - توقف فجأة عن فحص الشيكات على هذا السلم - كيف نفعل ذلك؟ لا شيء تقريبًا.
أيضا ، مع هذا النهج ،
لا توجد حماية من الدول الوهمية . عندما يتم وصف التحولات من خلال حالة مفتاح التبديل ، فمن المرجح أن يتم تنفيذ السلوك الافتراضي. هذه الحالة منطقية من حيث سلوك البرنامج ، لكنها بالكاد منطقية من حيث المنطق البشري أو التجاري للتطبيق.
ماذا يمكن أن يكون الحل لأوجه القصور المشار إليها؟ بالطبع ، هذا هو بناء منطق كل وحدة نمطية / وحدة تحكم / كائن معقد ، لا يعتمد على الحدس ، ولكن باستخدام نهج رسمي جيد. على سبيل المثال ، آلة حالة محدودة.
لعبة اللعب
كمثال ، نأخذ ما تقدمه Apple. ضمن إطار عمل GameplayKit ، هناك فئتان تساعداننا في العمل مع جهاز الحالة.
يوضح اسم الإطار أن شركة آبل تريد استخدامها في الألعاب. ولكن
في التطبيقات غير المخصصة للألعاب ، سيكون ذلك مفيدًا.

تحدد فئة
GKState الدولة. لوصف ذلك ، تحتاج إلى تنفيذ خطوات بسيطة. نرث من هذه الفئة ، ونعين اسم الدولة ، ونحدد ثلاث طرق.
- isValidNextState - ما إذا كانت الحالة الحالية صالحة بناءً على الحالة السابقة.
- didEnterFrom - الإجراءات عند الانتقال إلى هذه الحالة.
- willExitTo - الإجراءات عند الخروج من هذه الحالة.
GKStateMachine هي فئة من أجهزة الدولة. إنه أسهل. يكفي القيام بعملين.
- نقوم بتمرير مجموعة حالات الإدخال إلى الصفيف المكتوب من خلال المُهيئ.
- نقوم بعمل انتقالات اعتمادًا على إشارات الإدخال باستخدام طريقة الإدخال. من خلاله ، يتم تعيين الحالة الأولية أيضًا.
قد يكون من المربك أن يتم تمرير أي فئة كوسيطة لأسلوب
الإدخال . ولكن تجدر الإشارة إلى أنه لا يمكن تعريف كائن من أي فئة في مجموعة من الحالات
- وهذا يمنع الكتابة الصارمة. وفقًا لذلك ، إذا قمت بتعيين فئة عشوائية على أنها فئة الحالة التالية ، فلن يحدث شيء ، وستُرجع طريقة الإدخال خطأ.
الدول والتحولات بينهما
بعد أن تعرفت على إطار عمل Apple ، فلنعد إلى المثال. من الضروري وصف الحالات والتحولات بينها. تحتاج إلى القيام بذلك بأكثر طريقة يمكن فهمها. هناك خياران شائعان: جدول أو كرسم بياني انتقالي. في رأيي ، فإن الرسم البياني الانتقالي هو خيار أكثر قابلية للفهم. إنه في UML بطريقة موحدة. لذلك ، نختارها.
في الرسم البياني للانتقال ، هناك حالات تم وصفها بالأسماء ، والأسهم التي تربط هذه الحالات لوصف التحولات. في المثال ، هناك حالة أولية
- نتوقع بيانات
- وهناك ثلاث حالات يمكن الوصول إليها من الحالة الأولى: البيانات المستلمة ، لا توجد بيانات وخطأ.

في التنفيذ ، نحصل على أربع فئات صغيرة.

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

معلمات الآلة
الخطوة الثانية التي يجب القيام بها
هي تعيين معلمات آلة الحالة. للقيام بذلك ، قم بإنشاء حالات ونقلها إلى الكائن الآلي.

تأكد أيضًا من تعيين الحالة الأولية

من حيث المبدأ ، كل شيء ، الجهاز جاهز. الآن من الضروري معالجة ردود الفعل على الأحداث الخارجية ، وتغيير حالة الأوتوماتيكي.

أذكر بيان المشكلة. لقد حصلنا على سلم من if-else ، والذي تم على أساسه تحديد الإجراء الذي يجب القيام به. كعنصر تحكم لآلية بسيطة ، قد يكون خيار التنفيذ هذا (في الواقع ، مفتاحًا بسيطًا
- هذا هو تنفيذ بدائي لآلة حالة محدودة) ، لكننا لا نتخلص عمليًا من العوائق المذكورة سابقًا.
هناك نهج آخر يسمح لك بالابتعاد عن هذه السلالم. تقترحه كلاسيكيات البرمجة
- ما يسمى ب "عصابة الأربعة".

هناك نمط تصميم خاص يسمى "الحالة".

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

من الممارسات الجيدة وصف الحالة بكائن واحد وتمريرها دائمًا ، بدلاً من كتابة العديد من معلمات الإدخال. بعد ذلك ، نفوض اختيار الدولة التالية إلى الحالة الحالية. هذه هي الترقية الكاملة.
مزايا GameplayKit.- مكتبة قياسية. لا حاجة لتنزيل أي شيء ، استخدم cocoapods أو قرطاج.
- المكتبة سهلة التعلم.
- هناك تطبيقان في وقت واحد: على الهدف- C وعلى Swift.
العيوب:- ترتبط إدراك الدول والتحولات ارتباطًا وثيقًا.
انتهك مبدأ المسؤولية وحدها: فالدولة تعرف إلى أين تذهب وكيف. - لا يتم التحكم في الحالات المكررة بأي شكل من الأشكال.
يتم تمرير مصفوفة إلى جهاز الحالة ، وليس العديد من الحالات. إذا قمت بنقل عدة حالات متطابقة ، فسيتم استخدام آخر حالة من القائمة.
ما هي تطبيقات آلة الحالة المحدودة؟ ألق نظرة على GitHub.
تنفيذ الهدف جيم

TransitionKit
هذه هي مكتبة Objective-C الأكثر شيوعًا لفترة طويلة ، وخالية من العيوب المحددة في GamePlayKit. يسمح لنا بتنفيذ جهاز الدولة وجميع الإجراءات المرتبطة به على الكتل.
الدولة منفصلة عن التحولات .
يوجد في TransitionKit فئتين.
- TKState - لتحديد الحالات والمدخلات وإجراءات الإخراج.
- TKEvent عبارة عن فصل لوصف الانتقال.
TKEvent يربط بعض الدول بغيرها. يتم تعريف الحدث نفسه بسلسلة.
بالإضافة إلى ذلك ، هناك فوائد إضافية.
يمكنك نقل البيانات المفيدة أثناء النقل . يعمل هذا تمامًا مثل عند استخدام NSNotificationCenter. تأتي كل الحمولة النافعة على شكل قاموس userInfo ، ويحلل المستخدم المعلومات.
وصف الانتقال الخاطئ عندما نحاول إجراء انتقال غير موجود - مستحيل - لا نحصل على قيمة NO فقط عند العودة من طريقة الانتقال ، ولكن أيضًا على وصف تفصيلي للخطأ ، وهو أمر مفيد عند تصحيح أخطاء جهاز الحالة.

يتم استخدام TransitionKit في حصادة شبكة RestKit الشهيرة. هذا مثال جيد على كيفية استخدام جهاز الحالة في نواة التطبيق عند تنفيذ عمليات الشبكة.

يحتوي RestKit على فئة خاصة - RKOperationStateMachine - لإدارة العمليات المتزامنة. عند الإدخال ، يقبل العملية التي تتم معالجتها وقائمة الانتظار لتنفيذها.

داخليا ، آلة الحالة بسيطة للغاية: ثلاث حالات (جاهزة ، منفذة ، مكتملة) وتحولان: البدء والتنفيذ. بعد بدء المعالجة (وفي أي انتقالات) ، تبدأ آلة الحالة في التحكم في كتلة التعليمات البرمجية المعرفة بواسطة المستخدم في قائمة الانتظار المحددة عند إنشاء قائمة الانتظار.
تقوم العملية المرتبطة بأوتوماتها بنقل الأحداث الخارجية إلى الأوتوماتون ، وتقوم بإجراء انتقالات بين الدول وجميع الإجراءات ذات الصلة. آلة الدولة تعتني
- تنفيذ التعليمات البرمجية غير المتزامنة ،
- تنفيذ الشفرة الذرية خلال التحولات ،
- تحكم الانتقال
- إلغاء العمليات
- صحة تغيير متغيرات حالة العملية: isReady، isExecuting، is Finish.
التحول
بالإضافة إلى TransitionKit ، تجدر الإشارة إلى
Shift بشكل منفصل - مكتبة صغيرة تم تنفيذها كفئة فوق NSObject. يسمح لك هذا النهج بتحويل أي كائن إلى جهاز حالة ، يصف حالته في شكل ثوابت سلسلة وإجراءات في الكتل أثناء التحولات. بالطبع ، هذا أكثر من مشروع تدريب ، ولكنه مثير للاهتمام ويسمح لك بتجربة ما هي آلة الدولة بأقل تكلفة.
تطبيقات سريعة

هناك العديد من تطبيقات الآلة المحدودة على Swift. سوف أذكر واحدة (
ملاحظة : لسوء الحظ ، لم يتم تطوير المشروع في العامين الماضيين بعد التقرير ، لكن الأفكار الواردة فيه تستحق أن نقول في المقالة).
SwiftyStateMachine
في SwiftyStateMachine ، يتم تمثيل جهاز الحالة بهيكل غير مستقر ؛ من خلال طرق didSet للخاصية ، يمكنك بسهولة التقاط تغييرات الحالة.
في هذه المكتبة ، يتم تعريف آلة الحالة من خلال جدول مراسلات الدول والانتقالات بينها. يتم وصف هذا المخطط بشكل منفصل عن الكائن الذي ستتحكم فيه الماكينة. يتم تنفيذ ذلك من خلال مفتاح التبديل المتداخل.

, .
- .
, . - .
state machine , state machine. - , , .
- , DOT.
state- — DOT. , , .

الخلاصة
.
- .
, . , . , .
- .
( ).
- .
, , . - . , SwiftyStateMachine , , . .
- .
, . , , . .
.

. , . , , switch case: , , — .

. . , . , , , . .

, , . .

—
. : , — .


«-»
.

, . .
app coordinators — , , : , . , .
, app coordinator , state machine. . , app coordinators state machine, , , ,
. , , , . .

, state machine , , .
state machine , if-else. , .
هذا العام في Apps Conf 2018 ، الذي سيعقد في 8 و 9 أكتوبر ، يخطط ألكسندر لمناقشة خمسة مبادئ أساسية للبرمجة الشيئية وحدود تطبيقها.
لمزيد من تقارير تطوير الجوال ، تحقق من قناتنا على YouTube . وإذا كنت ترغب في تلقي معلومات حول النصوص الجديدة والتقارير المثيرة للاهتمام ، اشترك في النشرة الإخبارية .