استخدام Enum + القيم المرتبطة عند التنقل ونقل البيانات بين الشاشات في تطبيقات IOS

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



الخلفية:



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



  • الاستخدام الإلزامي لـ MVVM ؛
  • القدرة على إضافة شاشات جديدة بسرعة (وحدات التحكم ونماذج العرض الخاصة بهم) إلى عملية التنقل ؛
  • يجب ألا تؤثر التغييرات في منطق الأعمال على التنقل ؛
  • يجب ألا تؤثر التغييرات في التنقل على منطق الأعمال ؛
  • القدرة على إعادة استخدام الشاشات بسرعة دون إجراء تصحيحات على التنقل ؛
  • القدرة على الحصول بسرعة على فكرة عن الشاشات الموجودة ؛
  • القدرة على الحصول بسرعة على فكرة عن التبعيات في المشروع ؛
  • لا ترفع العتبة للمطورين لدخول المشروع.




أوضح ماذا تقصد



وتجدر الإشارة إلى أن الحل النهائي لم يتم تشكيله في يوم واحد ، ولا يخلو من عيوبه وهو أكثر ملاءمة للمشاريع الصغيرة والمتوسطة الحجم. للتوضيح ، يمكن الاطلاع على مشروع الاختبار هنا: github.com/ArturRuZ/NavigationDemo



1. لتتمكن من الحصول بسرعة على فكرة عن الشاشات الموجودة ، تقرر إنشاء تعداد باسم لا لبس فيه ControllersList.



enum ControllersList {
   case textInputScreen
   case textConfirmationScreen
}


2. لعدد من الأسباب ، لم يرغب المشروع في استخدام حلول الجهات الخارجية لـ DI ، وأردت الحصول على DI ، بما في ذلك القدرة على عرض التبعيات في المشروع بسرعة ، لذلك تقرر استخدام التجميع لكل شاشة منفصلة (مغلقة بواسطة بروتوكول التجميع) و RootAssembly باسم النطاق العام.




protocol Assembly {
   func build() -> UIViewController
}

final class TextInputAssembly: Assembly {
   func build() -> UIViewController {
      let viewModel = TextInputViewModel()
      return TextInputViewController(viewModel: viewModel)
   }
}

final class TextConfirmationAssembly: Assembly {
   private let text: String
   
   init(text: String) {
      self.text = text
   }
   
   func build() -> UIViewController {
      let viewModel = TextConfirmationViewModel(text: text)
      return TextConfirmationViewController(viewModel: viewModel)
   }
}


3. لنقل البيانات بين الشاشات (حيثما تكون هناك حاجة فعلية لها) ، تحولت قائمة التحكم إلى تعداد مع القيم المرتبطة:



enum ControllersList {
   case textInputScreen
   case textConfirmationScreen(text: String)
}


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




protocol Coordination {
   func show(view: ControllersList, firstPosition: Bool)
   func popFromCurrentController()
}

final class Coordinator {
   
   private var navigationController = UINavigationController()
   private var factory: ControllerBuilder?
   
   private func navigateWithFirstPositionInStack(to: UIViewController) {
      navigationController.viewControllers = [to]
   }
   private func navigate(to: UIViewController) {
      navigationController.pushViewController(to, animated: true)
   }
}

extension Coordinator: Coordination {
   func popFromCurrentController() {
      navigationController.popViewController(animated: true)
   }
   func show(view: ControllersList, firstPosition: Bool) {
      guard let controller = factory?.buildController(for: view) else { return }
                 firstPosition ?  navigateWithFirstPositionInStack(to: controller) : navigate(to: controller)
   }
}



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



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



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




//MARK - Dependences with controllers associations
fileprivate extension ControllersList {
   typealias scope = AssemblyServices
  
   var assembly: Assembly {
      switch self {
      case .textInputScreen:
         return TextInputAssembly(coordinator: scope.coordinator)
      case .textConfirmationScreen(let text):
         return TextConfirmationAssembly(coordinator: scope.coordinator, text: text)
      }
   }
}

//MARK - Services all time in memory
fileprivate enum AssemblyServices {
   static let coordinator: oordinationDependencesRegstration = Coordinator()
   static let controllerFactory: ControllerBuilderDependencesRegistration = ControllerFacotry()
}

//MARL: - RootAssembly Implementation
final class  RootAssembly {
   fileprivate typealias scope = AssemblyServices
  
   private func registerPropertyDependences() {
//     this place for propery dependences
   }
}


// MARK: - AssemblyDataSource implementation
extension RootAssembly: AssemblyDataSource {
   func getAssembly(key: ControllersList) -> Assembly? {
      return key.assembly
   }
}


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



خاتمة



هذا المثال هو تنفيذ مبسط للفكرة ولا يغطي جميع حالات الاستخدام ؛ ومع ذلك ، فقد أثبت النهج نفسه أنه مرن تمامًا وقابل للتكيف.



عيوب هذا النهج هي كما يلي:



  • , , . ControllersList NavigationEvents, , ;
  • , ;
  • , , . , .


تؤثر معظم المنشورات حول التنقل ونقل البيانات في تطبيقات IOS إما على استخدام المنسقين والموجهات (لكل أو مجموعة من الشاشات) ، أو التنقل من خلال segue ، و singleton ، وما إلى ذلك ، ولكن لم يناسبني أي من هذه الخيارات لهذا أو ذاك. أسباب.



ربما يناسبك هذا النهج لحل المشكلات ، شكرًا على وقتك!



All Articles