انتقل إلى وضع عدم الاتصال أولاً باستخدام البيانات الأساسية والمستند (المستندات) المُدارة

بعد انضمامه إلى MegaFon كمطور iOS ، دخل Valentin Chernov في الاتجاه الرئيسي اليوم - عدم الاتصال بالإنترنت: يقوم Valentin بتطوير حساب شخصي للهاتف المحمول - التطبيق الرئيسي لـ MegaFon. يتيح لك رؤية الرصيد وتغيير التعريفة وربط الخدمات والخدمات وفصلها والمشاركة في المسابقات واستخدام العروض الشخصية لشركاء MegaFon.



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



تحدث فالنتين عن كيفية تنفيذ هذه المهمة خلال الأشهر الخمسة الماضية ، وكيف تم اختيار بنية المشروع وتنفيذها ، وما هي التقنيات المستخدمة ، بالإضافة إلى ما حققته وما تم التخطيط له في المستقبل ، تحدث فالنتين في مؤتمر Apps Live 2020 لمطوري تطبيقات الهاتف المحمول.







مهمة



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



كومة التكنولوجيا



إلى جانب بنية MVC القياسية ، نستخدم:



Swift + Objective-C



معظم الكود (80٪ من مشروعنا) مكتوب بلغة Objective-C. وبالفعل نكتب رمزًا جديدًا في Swift.



العمارة المعيارية



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



الوحدات الفرعية (المكتبات)



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



البيانات الأساسية للتخزين المحلي



عند الاختيار ، كان المعيار الرئيسي بالنسبة لنا هو المواطنة والتكامل مع أطر عمل iOS. وكانت مزايا البيانات الأساسية حاسمة:



  • الحفظ التلقائي للمكدس والبيانات التي نتلقاها ؛
  • , ( , ..)
  • ;
  • ;
  • ;
  • ;
  • UI (FRC);
  • (NSPredicates).


UIManaged document



تحتوي مجموعة UI على فئة مضمنة تسمى UIManagedDocument ، وهي فئة فرعية من UIDocument. يتمثل الاختلاف الرئيسي في أنه عند تهيئة المستند المُدار ، يتم تحديد عنوان URL لموقع المستند في التخزين المحلي أو البعيد. يقوم كائن المستند بعد ذلك بإنشاء مكدس بيانات أساسية تمامًا خارج الصندوق ، والذي يتم استخدامه للوصول إلى التخزين الدائم للمستند باستخدام نموذج الكائن (.xcdatamodeld) من حزمة التطبيق الرئيسية. إنه مناسب ومعقول ، على الرغم من أننا نعيش بالفعل في القرن الحادي والعشرين:



  • يقوم UIDocument تلقائيًا بحفظ الحالة الحالية نفسها بتردد محدد. بالنسبة للأقسام الهامة بشكل خاص ، يمكننا تشغيل الحفظ يدويًا.
  • . - — , , - , — , , .
  • UIDocument .
  • Core data .
  • iCloud . , .
  • .
  • يتم استخدام نموذج التطبيق المستند إلى المستند - يمثل نموذج البيانات كحاوية لتخزين هذه البيانات. إذا نظرنا إلى نموذج MVC الكلاسيكي في وثائق Apple ، فيمكننا أن نرى أن البيانات الأساسية قد تم إنشاؤها على وجه التحديد لمعالجة هذا النموذج ومساعدتنا في العمل مع البيانات على مستوى أعلى من التجريد. على مستوى النموذج ، نعمل من خلال توصيل UIManagedDocument بالمكدس الذي تم إنشاؤه بالكامل. ونعتبر المستند نفسه بمثابة حاوية تخزن البيانات الأساسية وجميع البيانات من ذاكرة التخزين المؤقت (من الشاشات والمستخدمين). بالإضافة إلى أنه يمكن أن تكون الصور ومقاطع الفيديو والنصوص - أي معلومات.


نحن نعتبر تطبيقنا وإطلاقه وتفويض المستخدم وجميع بياناته نوعًا من المستندات الكبيرة (ملف) ، والتي تخزن تاريخ مستخدمنا:







معالجة



كيف صممنا العمارة



تتم عملية التصميم لدينا على عدة مراحل:



  1. تحليل المواصفات الفنية.
  2. تقديم مخطط UML. نستخدم بشكل أساسي ثلاثة أنواع من مخططات UML: مخطط الفصل ، مخطط التدفق ، مخطط التسلسل. هذه هي المسؤولية المباشرة لكبار المطورين ، لكن المطورين الأقل خبرة يمكنهم أيضًا القيام بذلك. هذا أمر مرحب به ، لأنه يتيح لك الغوص في المهمة جيدًا ومعرفة كل التفاصيل الدقيقة. يساعد ذلك في العثور على أي عيوب في المهمة الفنية ، وكذلك تنظيم جميع المعلومات المتعلقة بالمهمة. ونحاول أن نأخذ في الاعتبار طبيعة تطبيقنا عبر الأنظمة الأساسية - فنحن نعمل عن كثب مع فريق Android ، ونرسم نفس المخطط على نظامين أساسيين ونحاول استخدام أنماط التصميم الرئيسية المقبولة عمومًا من مجموعة الأربعة.
  3. مراجعة العمارة. كقاعدة عامة ، يقوم زميل من فريق مجاور بإجراء المراجعة والتقييم.
  4. التنفيذ والاختبار على مثال وحدة واحدة لواجهة المستخدم.
  5. تحجيم. إذا نجح الاختبار ، فإننا نوسع البنية عبر التطبيق بأكمله.
  6. إعادة بناء التعليمات البرمجية. للتحقق مما إذا فاتنا أي شيء.


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



ماذا حدث



كانت نقطة البداية لدينا هي بنية MVC القياسية - هذه طبقات مترابطة:



  • طبقة UI ، مبرمجة بالكامل باستخدام Objective C ؛
  • فئة العرض (نموذج) ؛
  • طبقة الخدمة التي نعمل بها مع الشبكة.


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







الفترة الانتقالية



خلال الفترة الانتقالية ، كان علينا تنفيذ التخزين المؤقت للشاشات. ولكن نظرًا لأن التطبيق كبير ويحتوي على الكثير من كود Objective C القديم ، فلا يمكننا فقط أخذ جميع الخدمات والنماذج وحذفها عن طريق إدخال رمز Swift - يجب أن نأخذ في الاعتبار أنه بالتوازي مع التخزين المؤقت ، لا يزال لدينا العديد من مهام المنتج الأخرى قيد التطوير.



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



على الجانب الأيمن من الرسم البياني ، قمنا بتقسيم الحصول على البيانات إلى أوامر - يهدف نمط الأوامر إلى تنفيذ بعض الأوامر الأساسية والحصول على النتيجة. في حالة iOS ، نستخدم ورثة NSOperation:







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



والمهمة الرئيسية للعمليات هي تمرير بيانات DataSource إلى DataSourceFacade. بالطبع ، نحن نبني المنطق بطريقة تظهر البيانات للمستخدم في أسرع وقت ممكن. عادةً ، داخل DataSourceFacade ، لدينا قائمة انتظار تشغيلية حيث نبدأ عمليات NSO الخاصة بنا. اعتمادًا على الظروف التي تم تكوينها ، يمكننا تحديد وقت إظهار البيانات من ذاكرة التخزين المؤقت ومتى يتم تلقيها من الشبكة. عند الطلب الأول لمصدر بيانات في الواجهة ، نذهب إلى قاعدة بيانات Core ، ونحصل على البيانات من هناك عبر FetchCommand (إن وجد) ونعيدها على الفور إلى المستخدم.



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



كيف اصبح



نذهب إلى مثل هذا المخطط الأكثر اقتضابًا (وسنأتي في النهاية):







الآن من هذا لدينا:



  • طبقة واجهة المستخدم ،
  • الواجهة التي نقدم من خلالها DataSource ،
  • الأمر الذي يُرجع مصدر البيانات هذا مع التحديثات.


ما هو مصدر البيانات ولماذا نتحدث عنه كثيرًا



مصدر البيانات هو كائن يوفر بيانات لطبقة العرض ويتبع بروتوكولًا محددًا مسبقًا. ويجب تعديل البروتوكول وفقًا لواجهة المستخدم الخاصة بنا وتقديم بيانات لواجهة المستخدم الخاصة بنا (لا يهم لشاشة معينة أو لمجموعة من الشاشات).



عادةً ما يكون لمصدر البيانات مسؤوليتين رئيسيتين:



  1. توفير البيانات للعرض في طبقة واجهة المستخدم ؛
  2. إخطار واجهة المستخدم الخاصة بالطبقة بتغييرات البيانات وإرسال مجموعة التغييرات اللازمة إلى الشاشة عندما نتلقى تحديثًا.


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



مثال لأحد مصادر البيانات لدينا:







هذا مصدر بيانات لمجموعة (يطلق عليه CollectionDataSource) وهذه فئة بسيطة إلى حد ما من وجهة نظر الواجهة. يستغرق مجموعة تم تكوينها بواسطة fetchedResultsController و CellDequeueBlock. حيث CellDequeueBlock هو اسم مستعار من النوع الذي نصف فيه استراتيجية إنشاء الخلايا.



أي أننا أنشأنا DataSource وقمنا بتعيينه للمجموعة عن طريق استدعاء PerformFetch على fetchedResultsController ، ثم يتم تعيين كل السحر لتفاعل فئة DataSource الخاصة بنا ، fetchedResultsController وقدرة المفوض على تلقي التحديثات من قاعدة البيانات:







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



وحيثما يكون من الممكن استخدام DataSource الجاهزة مع الجداول أو المجموعات ، فإننا نقوم بذلك:







في تلك الأماكن التي لدينا فيها الكثير من الشاشات ولا نستخدم الجداول والمجموعات (ونستخدم تخطيط برنامج Objective C) ، نقوم بتقييم البيانات التي نحتاجها للشاشة ، ومن خلال البروتوكول نصف DataSource الخاص بنا. بعد ذلك ، نكتب الواجهة - كقاعدة عامة ، هذا أيضًا بروتوكول Objective C عام نطلب من خلاله مصدر البيانات الخاص بنا. ومن ثم فإن مدخل كود Swift قيد التقدم بالفعل.



بمجرد أن نكون مستعدين لنقل الشاشة بالكامل إلى تطبيق Swift ، فسيكون ذلك كافيًا لإزالة غلاف Objective C - وبفضل DataSource المخصص ، يمكننا العمل مباشرةً مع بروتوكول Swift.



نحن نستخدم حاليًا ثلاثة أنواع رئيسية من DataSources:

  1. TableViewDatasource + إستراتيجية الخلية (إستراتيجية إنشاء الخلايا) ؛
  2. CollectionViewDatasource + استراتيجية الخلية (خيار مع مجموعات) ؛
  3. CustomDataSource هو خيار مخصص. نحن نستخدمها أكثر من غيرها الآن.




النتائج



بعد كل خطوات التصميم والتنفيذ والتفاعل مع الكود القديم ، تلقت الشركة التحسينات التالية:



  • زادت سرعة تسليم البيانات للمستخدم بشكل كبير بسبب التخزين المؤقت. ربما تكون هذه نتيجة واضحة ومنطقية.
  • نحن الآن نقترب خطوة واحدة من النموذج الأول غير المتصل بالإنترنت.
  • تم إعداد عمليات المراجعة المعمارية عبر الأنظمة الأساسية داخل فرق iOS و Android - يمتلك جميع المطورين المشاركين في هذا المشروع المعلومات ويتبادلون الخبرات بسهولة بين الفرق.
  • . , , legacy , .
  • , — . , , , , , .


كانت المكافأة بالنسبة لنا هي أننا فهمنا كيف أن العمل مع الهندسة المعمارية والرسوم البيانية يمكن أن يكون ممتعًا وممتعًا (وهذا يبسط عملية التطوير). نعم ، لقد أمضينا الكثير من الوقت في رسم مناهجنا المعمارية ومواءمتها ، ولكن عندما يتعلق الأمر بالتنفيذ ، قمنا بالتوسع بسرعة كبيرة عبر جميع الشاشات.



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



الروابط





Apps Live 2020 .

— Android iOS, . , , .




All Articles