إنشاء بنية: العمل مع نمط منسق iOS


( توضيح )

يبدأ كل فريق عاجلاً أم آجلاً في التفكير في تقديم مناهجه المعمارية الخاصة به ، وتم كسر العديد من النسخ حوله. لذلك في Umbrella IT ، أردنا دائمًا العمل باستخدام أدوات مرنة حتى لا يكون تشكيل الهندسة المعمارية أمرًا مؤلمًا ، وتوقفت مشكلات التنقل والملفات الوهمية والعزلة والاختبار إلى أن تكون أمرًا مخيفًا ، شيء عاجلاً أم آجلاً معلقة على مشروع متضخم. لحسن الحظ ، نحن لا نتحدث عن بنية "حصرية" جديدة ذات اختصار متظاهر. يجب أن أعترف بأن البنايات الشائعة حاليًا (MVP ، MVVM ، VIPER ، Clean-swift) تتعامل مع مهامها ، وأن الاختيار الخاطئ والاستخدام الخاطئ لهذا النهج أو ذاك يمكن أن يسبب صعوبات. ومع ذلك ، في إطار الهيكل المعتمد ، يمكن استخدام أنماط مختلفة ، مما سيسمح بتحقيق تلك المؤشرات الأسطورية تقريبًا: المرونة والعزل والاختبار وإعادة الاستخدام.

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

1. تنفيذ البروتوكولات الرئيسية


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

protocol Coordinatable: class { func start() } protocol Presentable { var toPresent: UIViewController? { get } } extension UIViewController: Presentable { var toPresent: UIViewController? { return self } func showAlert(title: String, message: String? = nil) { UIAlertController.showAlert(title: title, message: message, inViewController: self, actionBlock: nil) } } 

في هذا المثال ، يعد showAlert مجرد طريقة مناسبة لاستدعاء إعلام ، وهو موجود في ملحق UIViewController.

 protocol Routable: Presentable { func present(_ module: Presentable?) func present(_ module: Presentable?, animated: Bool) func push(_ module: Presentable?) func push(_ module: Presentable?, animated: Bool) func push(_ module: Presentable?, animated: Bool, completion: CompletionBlock?) func popModule() func popModule(animated: Bool) func dismissModule() func dismissModule(animated: Bool, completion: CompletionBlock?) func setRootModule(_ module: Presentable?) func setRootModule(_ module: Presentable?, hideBar: Bool) func popToRootModule(animated: Bool) } 

2. إنشاء منسق


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

نشرع في تنفيذ طبقة الإحداثيات:



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

تحتاج أولاً إلى إضافة الإخراج إلى المنسق حتى يتمكن من الاتصال بمنسق أعلى (AppCoordinator):

 protocol AuthorizationCoordinatorOutput: class { var finishFlow: CompletionBlock? { get set } } final class AuthorizationCoordinator: BaseCoordinator, AuthorizationCoordinatorOutput { var finishFlow: CompletionBlock? fileprivate let factory: AuthorizationFactoryProtocol fileprivate let router : Routable init(router: Routable, factory: AuthorizationFactoryProtocol) { self.router = router self.factory = factory } } // MARK:- Coordinatable extension AuthorizationCoordinator: Coordinatable { func start() { performFlow() } } // MARK:- Private methods private extension AuthorizationCoordinator { func performFlow() { //:- Will implement later } } 



كما هو موضح أعلاه ، لدينا منسق تخويل مع جهاز توجيه ومصنع وحدة. ولكن من ومتى يستدعي طريقة start ()؟
هنا نحتاج إلى تنفيذ AppCoordinator.

 final class AppCoordinator: BaseCoordinator { fileprivate let factory: CoordinatorFactoryProtocol fileprivate let router : Routable fileprivate let gateway = Gateway() init(router: Routable, factory: CoordinatorFactory) { self.router = router self.factory = factory } } // MARK:- Coordinatable extension AppCoordinator: Coordinatable { func start() { self.gateway.getState { [unowned self] (state) in switch state { case .authorization: self.performAuthorizationFlow() case .main: self.performMainFlow() } } } } // MARK:- Private methods func performAuthorizationFlow() { let coordinator = factory.makeAuthorizationCoordinator(with: router) coordinator.finishFlow = { [weak self, weak coordinator] in guard let `self` = self, let `coordinator` = coordinator else { return } self.removeDependency(coordinator) self.start() } addDependency(coordinator) coordinator.start() } func performMainFlow() { // MARK:- main flow logic } 

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

 final class CoordinatorFactory { fileprivate let modulesFactory = ModulesFactory() } extension CoordinatorFactory: CoordinatorFactoryProtocol { func makeAuthorizationCoordinator(with router: Routable) -> Coordinatable & AuthorizationCoordinatorOutput { return AuthorizationCoordinator(router: router, factory: modulesFactory) } } 

3. تنفيذ منسقي المصنع


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

 class BaseCoordinator { var childCoordinators: [Coordinatable] = [] // Add only unique object func addDependency(_ coordinator: Coordinatable) { for element in childCoordinators { if element === coordinator { return } } childCoordinators.append(coordinator) } func removeDependency(_ coordinator: Coordinatable?) { guard childCoordinators.isEmpty == false, let coordinator = coordinator else { return } for (index, element) in childCoordinators.enumerated() { if element === coordinator { childCoordinators.remove(at: index) break } } } } 

BaseCoordinator - فصل يحتوي على مجموعة من المنسقين الفرعيين وطريقتين: حذف وإضافة تبعية المنسق.

4. تكوين AppDelegate


الآن دعونا نرى كيف يبدو UIApplicationMain:

 @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var rootController: UINavigationController { window?.rootViewController = UINavigationController() window?.rootViewController?.view.backgroundColor = .white return window?.rootViewController as! UINavigationController } fileprivate lazy var coordinator: Coordinatable = self.makeCoordinator() func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { coordinator.start() return true } } // MARK:- Private methods private extension AppDelegate { func makeCoordinator() -> Coordinatable { return AppCoordinator(router: Router(rootController: rootController), factory: CoordinatorFactory()) } } 

بمجرد أن يتم استدعاء طريقة التفويض ، تم استدعاء الأسلوب start () لـ AppCoordinator ، والذي سيحدد المنطق الإضافي للتطبيق.

5. إنشاء وحدة شاشة


لتوضيح ما سيحدث بعد ذلك ، دعنا نعود إلى AuthorizationCoordinator وننفذ أسلوب PerformFlow ().

أولاً ، نحتاج إلى تنفيذ واجهة AuthorizationFactoryProtocol في فئة ModulesFactory:

 final class ModulesFactory {} // MARK:- AuthorizationFactoryProtocol extension ModulesFactory: AuthorizationFactoryProtocol { func makeEnterView() -> EnterViewProtocol { let view: EnterViewController = EnterViewController.controllerFromStoryboard(.authorization) EnterAssembly.assembly(with: view) return view 

من خلال استدعاء أي طريقة في مصنع الوحدة النمطية ، كقاعدة ، نعني تهيئة ViewController من لوحة العمل ، ثم ربط جميع المكونات الضرورية لهذه الوحدة ضمن بنية محددة (MVP ، MVVM ، CleanSwift).

بعد الاستعدادات اللازمة ، يمكننا تنفيذ طريقة PerformFlow () الخاصة بـ AuthorizationCoordinator.
شاشة البدء داخل هذا المنسق هي EnterView.
في طريقة PerformFlow () ، باستخدام مصنع الوحدة النمطية ، يُطلق على إنشاء وحدة جاهزة للمنسق المحدد ، ثم يتم تنفيذ منطق معالجة عمليات الإغلاق التي يستدعيها جهاز التحكم في العرض في وقت أو آخر ، ثم يتم إعداد هذه الوحدة من قبل جهاز التوجيه كجذر في كومة التنقل للشاشات:

 private extension AuthorizationCoordinator { func performFlow() { let enterView = factory.makeEnterView() finishFlow = enterView.onCompleteAuthorization enterView.output?.onAlert = { [unowned self] (message: String) in self.router.toPresent?.showAlert(message: message) } router.setRootModule(enterView) } } 




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

رابط إلى github ، الذي يحتوي على عرض توضيحي يعتمد على هندسة نظيفة ونموذج Xcode مناسب لإنشاء الطبقات المعمارية اللازمة.

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


All Articles