Menggunakan Enum + Associated Values ​​saat Menavigasi dan Mentransfer Data Antar Layar di Aplikasi IOS

Dalam posting ini, saya ingin menyentuh pertanyaan kuno tentang mengatur navigasi dan transfer data antar layar di aplikasi IOS. Pertama-tama, saya ingin menyampaikan konsep pendekatan saya, dan tidak meyakinkan Anda untuk menggunakannya sebagai pil ajaib. Di sini kami tidak akan mempertimbangkan berbagai pendekatan arsitektural atau kemungkinan menggunakan UlStoryboard dengan segues, secara umum saya akan menjelaskan cara lain yang mungkin untuk mencapai apa yang Anda inginkan dengan pro dan kontra. Jadi, mari kita mulai!



Latar Belakang:



Tentu saja, pilihan pendekatan arsitektural memengaruhi implementasi navigasi dan pengorganisasian transportasi data dalam suatu proyek, namun, pendekatan itu sendiri terdiri dari sejumlah keadaan: komposisi tim, waktu ke pasar, keadaan spesifikasi teknis, skalabilitas proyek, dll.



  • penggunaan MVVM secara wajib;
  • kemampuan untuk menambahkan layar baru dengan cepat (pengontrol dan model tampilan) ke proses navigasi;
  • perubahan dalam logika bisnis seharusnya tidak mempengaruhi navigasi;
  • perubahan navigasi seharusnya tidak mempengaruhi logika bisnis;
  • kemampuan untuk menggunakan kembali layar dengan cepat tanpa melakukan koreksi pada navigasi;
  • kemampuan untuk mendapatkan gambaran dengan cepat tentang layar yang ada;
  • kemampuan untuk dengan cepat mendapatkan gambaran tentang dependensi dalam proyek;
  • jangan menaikkan ambang batas bagi pengembang untuk memasuki proyek.




Langsung ke intinya



Perlu dicatat bahwa solusi akhir tidak terbentuk dalam satu hari, bukan tanpa kekurangannya dan lebih cocok untuk proyek kecil dan menengah. Untuk kejelasan, proyek pengujian dapat dilihat di sini: github.com/ArturRuZ/NavigationDemo



1. Agar dapat dengan cepat mendapatkan gambaran tentang layar yang ada, diputuskan untuk membuat enum dengan nama ControllersList yang tidak ambigu.



enum ControllersList {
   case textInputScreen
   case textConfirmationScreen
}


2. Karena sejumlah alasan, proyek tidak ingin menggunakan solusi pihak ketiga untuk DI, dan saya ingin mendapatkan DI, termasuk dengan kemampuan untuk melihat dependensi dalam proyek dengan cepat, jadi diputuskan untuk menggunakan Assembly untuk setiap layar terpisah (ditutup oleh protokol Assembly) dan RootAssembly sebagai ruang lingkup umum.




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. Untuk mentransfer data antar layar (jika memang diperlukan) ControllersList berubah menjadi enum dengan Associated Values:



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


4. Agar logika bisnis tidak memengaruhi navigasi, maupun navigasi pada logika bisnis, serta menggunakan kembali layar dengan cepat, navigasi perlu dipindahkan ke lapisan terpisah. Beginilah cara Koordinator dan protokol Koordinasi muncul:




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)
   }
}



Penting untuk dicatat di sini bahwa protokol dapat menjelaskan lebih banyak metode, termasuk. seperti Koordinator, ia dapat mengimplementasikan berbagai protokol, bergantung pada kebutuhan.



5. Dengan semua ini, saya juga ingin membatasi serangkaian tindakan yang harus dilakukan pengembang dengan menambahkan layar baru ke aplikasi. Saat ini, perlu diingat bahwa di suatu tempat Anda perlu mendaftarkan dependensi, dan dimungkinkan untuk melakukan beberapa tindakan lain agar navigasi berfungsi.



6. Saya tidak ingin membuat router dan koordinator tambahan sama sekali. Selain itu, membuat logika tambahan untuk navigasi dapat mempersulit persepsi navigasi dan penggunaan kembali layar secara signifikan. Semua ini mengarah pada rantai perubahan yang pada akhirnya terlihat seperti ini:




//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
   }
}


Sekarang, saat membuat layar baru, pengembang hanya perlu membuat perubahan pada ControllersList, dan kemudian kompilator itu sendiri menunjukkan di mana perlu melakukan perubahan. Menambahkan layar baru ke ControllersList sama sekali tidak memengaruhi skema navigasi saat ini, dan logika manajemen ketergantungan mudah diikuti. Selain itu, menggunakan ControllersList, Anda dapat dengan mudah menemukan semua titik masuk ke layar tertentu, dan sekarang mudah untuk menggunakan kembali layar.



Kesimpulan



Contoh ini adalah implementasi gagasan yang disederhanakan dan tidak mencakup semua kasus penggunaan; namun, pendekatan itu sendiri terbukti cukup fleksibel dan adaptif.



Kerugian dari pendekatan ini adalah sebagai berikut:



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


Sebagian besar posting tentang navigasi dan transfer data di aplikasi IOS memengaruhi penggunaan koordinator dan router (untuk masing-masing atau sekelompok layar), atau navigasi melalui segue, singleton, dll., Tetapi tidak ada dari opsi ini yang cocok untuk saya satu sama lain. alasan.



Mungkin pendekatan ini cocok untuk Anda untuk memecahkan masalah, terima kasih atas waktu Anda!



All Articles