نظف العمارة السريعة كبديل لـ VIPER

مقدمة


في الوقت الحالي ، هناك العديد من المقالات حول VIPER - العمارة النظيفة ، والتي اختلفت أشكالها المختلفة في وقت واحد لمشاريع iOS. إذا لم تكن على دراية بـ Viper ، يمكنك قراءتها هنا أو هنا أو هنا .

أود أن أتحدث عن بديل VIPER - Clean Swift. يبدو Clean Swift للوهلة الأولى مثل VIPER ، ومع ذلك ، تصبح الاختلافات مرئية بعد دراسة مبدأ التفاعل بين الوحدات. في VIPER ، يعتمد التفاعل على مقدم العرض ، فهو ينقل طلبات المستخدم إلى Interactor لمعالجة وتنسيق البيانات الواردة منه مرة أخرى للعرض على View Controller:

الصورة

في Clean Swift ، الوحدات الرئيسية ، كما هو الحال في VIPER ، هي View Viewer و Interactor و Presenter.

الصورة

يحدث التفاعل بينهما في دورات. يعتمد نقل البيانات على البروتوكولات (مرة أخرى ، على غرار VIPER) ، والتي تسمح للتغييرات المستقبلية في أحد مكونات النظام لاستبدالها بآخر. تبدو عملية التفاعل بشكل عام كما يلي: ينقر المستخدم على الزر ، وينشئ View Controller كائنًا مع وصف ويرسله إلى Interactor. يقوم Interactor ، بدوره ، بتنفيذ سيناريو محدد وفقًا لمنطق الأعمال ، ويقوم بإنشاء كائن نتيجة وإرساله إلى مقدم العرض. يقوم المقدِّم بتشكيل كائن ببيانات مهيأة للعرض على المستخدم وإرساله إلى "وحدة تحكم العرض". دعونا نلقي نظرة فاحصة على كل وحدة من وحدات Clean Swift بمزيد من التفصيل.

عرض (وحدة تحكم العرض)


تقوم أداة التحكم في العرض ، كما هو الحال في VIPER ، بتنفيذ جميع تكوينات VIew ، سواء كانت إعدادات اللون أو UILabel أو Layout. لذلك ، يقوم كل UIViewController في هذه البنية بتنفيذ بروتوكول إدخال لعرض البيانات أو الاستجابة لإجراءات المستخدم.

متفاعل


يحتوي Interactor على كل منطق الأعمال. يقبل إجراءات المستخدم من وحدة التحكم ، مع المعلمات (على سبيل المثال ، تغيير نص حقل الإدخال ، والضغط على زر) المحدد في بروتوكول الإدخال. بعد العمل على المنطق ، يجب على Interactor ، إذا لزم الأمر ، نقل البيانات لإعدادها إلى مقدم العرض قبل عرضها في ViewController. ومع ذلك ، لا يقبل Interactor إلا الطلبات من View كمدخل ، على عكس VIPER ، حيث تمر هذه الطلبات من خلال Presenter.

مقدم


مقدم العرض يعالج البيانات للعرض للمستخدم. النتيجة في هذه الحالة هي بروتوكول إدخال ViewController ، هنا يمكنك ، على سبيل المثال ، تغيير تنسيق النص ، وترجمة قيمة اللون من التعداد إلى RGB ، إلخ.

عامل


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

جهاز التوجيه


جهاز التوجيه مسؤول عن نقل البيانات إلى وحدات أخرى والانتقالات بينها. لديه رابط إلى وحدة التحكم ، لأنه في iOS ، للأسف ، وحدات التحكم ، من بين أمور أخرى ، هي المسؤولة تاريخيا عن التحولات. يمكن أن يؤدي استخدام segig إلى تبسيط تهيئة الانتقالات من خلال استدعاء طرق جهاز التوجيه من Prepare for segig ، لأن جهاز التوجيه يعرف كيفية نقل البيانات ، وسوف يفعل ذلك بدون أي رمز حلقة إضافي من Interactor / Presenter. يتم نقل البيانات باستخدام بروتوكولات مستودع البيانات لكل وحدة نمطية يتم تنفيذها في Interactor. تحد هذه البروتوكولات أيضًا من القدرة على الوصول إلى بيانات الوحدة الداخلية من جهاز التوجيه.

النماذج


النماذج هي وصف لهياكل البيانات لنقل البيانات بين الوحدات. كل تنفيذ لوظيفة منطق الأعمال له وصفه الخاص للنماذج.

  • طلب - لإرسال طلب من وحدة التحكم إلى المتفاعل.
  • الاستجابة - استجابة المتفاعل للإرسال إلى مقدم البيانات.
  • ViewModel - لنقل البيانات في نموذج جاهز للعرض في وحدة التحكم.

مثال على التنفيذ


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

مثال على بروتوكول الإدخال:

protocol ContactListDisplayLogic: class { func displayContacts(viewModel: ContactList.ShowContacts.ViewModel) } 

تحتوي كل وحدة تحكم على مرجع إلى كائن يقوم بتنفيذ بروتوكول Interactor الإدخال

 var interactor: ContactListBusinessLogic? 

بالإضافة إلى كائن جهاز التوجيه ، الذي يجب أن ينفذ منطق نقل البيانات وتبديل الوحدة:

 var router: (NSObjectProtocol & ContactListRoutingLogic & ContactListDataPassing)? 

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

 private func setup() { let viewController = self let interactor = ContactListInteractor() let presenter = ContactListPresenter() let router = ContactListRouter() viewController.interactor = interactor viewController.router = router interactor.presenter = presenter presenter.viewController = viewController router.viewController = viewController router.dataStore = interactor } 

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

 override func awakeFromNib() { super.awakeFromNib() AddContactConfigurator.sharedInstance.configure(self) } 

يحتوي رمز المكوِّن على طريقة التكوين الوحيدة المطابقة تمامًا لطريقة الإعداد في وحدة التحكم:

 final class AddContactConfigurator { static let sharedInstance = AddContactConfigurator() private init() {} func configure(_ control: AddContactViewController) { let viewController = control let interactor = AddContactInteractor() let presenter = AddContactPresenter() let router = AddContactRouter() viewController.interactor = interactor viewController.router = router interactor.presenter = presenter presenter.viewController = viewController router.viewController = viewController router.dataStore = interactor } } 

نقطة أخرى مهمة جدًا في تنفيذ وحدة التحكم هي الشفرة في المعيار التحضير لطريقة القطع:

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let scene = segue.identifier { let selector = NSSelectorFromString("routeTo\(scene)WithSegue:") if let router = router, router.responds(to: selector) { router.perform(selector, with: segue) } } } 

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

 func routeToViewContact(segue: UIStoryboardSegue?) 

يبدو طلب عرض البيانات إلى Interactor بسيطًا جدًا:

 private func fetchContacts() { let request = ContactList.ShowContacts.Request() interactor?.showContacts(request: request) } 

دعنا ننتقل إلى Interactor. تطبق Interactor بروتوكول ContactListDataStore ، المسؤول عن تخزين / الوصول إلى البيانات. في حالتنا ، هذه مجرد مجموعة من جهات الاتصال ، محدودة فقط من خلال طريقة getter ، لتظهر للموجه عدم جواز تغييره من وحدات أخرى. بروتوكول تنفيذ منطق الأعمال لقائمتنا هو كما يلي:

 func showContacts(request: ContactList.ShowContacts.Request) { let contacts = worker.getContacts() self.contacts = contacts let response = ContactList.ShowContacts.Response(contacts: contacts) presenter?.presentContacts(response: response) } 

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

 var presenter: ContactListPresentationLogic? 

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

 func presentContacts(response: ContactList.ShowContacts.Response) { let mapped = response.contacts.map { ContactList .ShowContacts .ViewModel .DisplayedContact(firstName: $0.firstName.uppercaseFirst, lastName: $0.lastName.uppercaseFirst) } let viewModel = ContactList.ShowContacts.ViewModel(displayedContacts: mapped) viewController?.displayContacts(viewModel: viewModel) } 

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

 func displayContacts(viewModel: ContactList.ShowContacts.ViewModel) { displayedContacts = viewModel.displayedContacts tableView.reloadData() } 

هذه هي الطريقة التي تبدو بها نماذج عرض جهات الاتصال:

 enum ShowContacts { struct Request { } struct Response { var contacts: [Contact] } struct ViewModel { struct DisplayedContact { let firstName: String let lastName: String var fullName: String { return firstName + " " + lastName } } var displayedContacts: [DisplayedContact] } } 

في هذه الحالة ، لا يحتوي الطلب على بيانات ، لأن هذه مجرد قائمة جهات اتصال عامة ، ومع ذلك ، إذا كانت شاشة القائمة ، على سبيل المثال ، تحتوي على مرشح ، يمكن تضمين نوع المرشح في هذا الطلب. يحتوي نموذج استجابة Intrecator على قائمة جهات الاتصال المطلوبة ، كما يحتوي ViewModel على مجموعة من البيانات الجاهزة للعرض - DisplayedContact.

لماذا كلين سويفت


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

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

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

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

لا ينبغي استخدام Clean Swift في المشاريع الصغيرة بدون منظور طويل المدى في مشاريع النماذج الأولية. على سبيل المثال ، من المكلف للغاية تطبيق تطبيق لجدول مؤتمر المطور باستخدام هذه البنية. على النقيض من ذلك ، تتناسب المشاريع طويلة الأجل ، والمشاريع مع الكثير من منطق الأعمال ، بشكل جيد في إطار هذه البنية. من الملائم جدًا استخدام Clean Swift عند تنفيذ المشروع لمنصتين - Mac OS و iOS ، أو من المخطط نقله في المستقبل.

Source: https://habr.com/ru/post/ar415725/


All Articles