تطوير وحدات أو وسيلة هناك ، وليس الى الوراء


كيف توصلنا إلى مقاربة جديدة للعمل مع الوحدات النمطية في تطبيق RaiffeisenBank iOS.

المشكلة


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

ما الصعوبات التي واجهناها:


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

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

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

الحل السابق: // الأعمال السيئة


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

لقد كانت خطوة للأمام ، ولكن ظهرت صعوبات جديدة:

  • واجهة الوكيل لم تضمن تنفيذ بروتوكول الإدخال ؛
  • كان لابد من وصف المخرجات ، حتى لو لم تكن مطلوبة ؛
  • كان من الضروري إضافة خاصية الإخراج إلى واجهة الإدخال .

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

بشكل عام ... فطيرة الأولى متكتل ، أصبح من الواضح أنك بحاجة إلى حل المشكلة بشكل مختلف.

الحل: // النهائي


مع إدراك أنه لم يكن هناك شيء آخر مع NSProxy ، فقد حملنا علامات وذهبنا إلى رسم. نتيجة لذلك ، عزلنا بروتوكول RFModule :

@objc protocol RFModule { var view: ViewController { get } var input: AnyObject? { get } var output: AnyObject? { get set } var transition: Transitioning { get set } } 

لقد تخلينا عن قصد عن الأنواع المرتبطة على مستوى البروتوكول ، وكان هناك سبب وجيه لذلك: في ذلك الوقت ، كان 90 ٪ من الكود في الهدف- C. التوافقية بين الوحدات ObjC ← → سويفت لن يكون ممكنا.

من أجل الاستمرار في استخدام الوراثة وضمان الاستخدام المكتوب للوحدات النمطية ، قدمنا ​​فئة الوحدة النمطية التي ترضي البروتوكول
RFModule :

 final class Module<I: Any, O: Any>: RFModule { public typealias Input = I public typealias Output = O public var setOutput: ((O?) -> Void)? //... public var input: I? { get { return inputObjc as? I} set { inputObjc = newValue as AnyObject } } public var output: O? { get { return outputObjc as? O} set { outputObjc = newValue as AnyObject } } @objc(input) public weak var inputObjc: AnyObject? @objc(moduleOutput) public weak var outputObjc: AnyObject? { didSet{ setOutput?(output) } } } @objc protocol RFModule { var view: ViewController { get } @objc(input) var inputObjc: AnyObject? { get } @objc(moduleOutput) var outputObjc: AnyObject? { get set } var transition: Transitioning { get set } } public extension RFModule { public var input: AnyObject? { return inputObjc } public var output: AnyObject? { get { return outputObjc } set { outputObjc = newValue} } } 

حتى وصلنا وحدة نمطية. وفي الواقع ، يستخدم سويفت الوحدة النمطية للفئة ، وفي Objective-C RFModule . بالإضافة إلى ذلك ، اتضح أنها أداة مناسبة لضبط الأنواع في المكان الذي تحتاج إلى إنشاء صفائف: على سبيل المثال ، TabContainer .

نظرًا لأن DI لإنشاء الوحدة النمطية في نطاق UserStory ، فإن تعيين قيمة الإخراج في المكان الذي سيتم استخدامه فيه لا يمكن أن يصف أداة ضبط بسيطة. "SetOutput" هو ، في جوهره ، تعريف ، في مرحلة تعيين الإخراج ، سينقله إلى الشخص المسؤول ، وفقًا لمنطق الوحدة النمطية.

 class SomeViewController: UIViewController, ModuleInput { weak var delegate: ModuleOutput } class Assembly { func someModule() -> Module<ModuleInput, ModuleOutput> { let view = SomeViewController() let module = Module<ModuleInput, ModuleOutput>(view: view, input: view) { [weak view] output in view?.delegate = output } return module } } ... let assembly: Assembly let module = assembly.someModule() module.output = self 

Transitioning هو بروتوكول تكون تطبيقاته ، كما يوحي الاسم ، مسؤولة عن منطق إظهار الوحدة النمطية وإخفائها.

 protocol Transitioning { var destination: ViewController? { get } // should be weak func perform(_ completion: (()->())?) // present func reverse(_ completion: (()->())?) // dissmiss } 

للعرض هو سبب - أداء ، للاختباء - عكس . على الرغم من حقيقة أن هناك وجهة في البروتوكول وفي البداية يبدو أنه يجب أن يكون هناك مصدر . في الواقع ، قد لا يكون المصدر ، ونوعه ليس دائماً ViewController . على سبيل المثال ، إذا كنا بحاجة إلى فتح الوحدة في نافذة جديدة ، فهذه نافذة ، وإذا احتجنا إلى التضمين ، فإننا نحتاج AND الأصل: ViewController AND container: UIView .

 class PresentTransition: Transitioning { weak var source: ViewController? weak var destination: ViewController? ... func perform(_ completion: (()->())?) { source.present(viewController: self.destinaton) } } 

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

هل هذا كل شيء؟


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

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

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


All Articles