Membuat arsitektur: bekerja dengan pola Koordinator iOS


( Ilustrasi )

Setiap tim cepat atau lambat mulai berpikir untuk memperkenalkan pendekatan arsitekturalnya sendiri, dan banyak salinannya dipecahkan. Jadi di Umbrella IT kami selalu ingin bekerja dengan alat yang fleksibel sehingga pembentukan arsitektur bukanlah sesuatu yang menyakitkan, dan masalah navigasi, file tiruan, isolasi dan pengujian berhenti menjadi sesuatu yang menakutkan, sesuatu yang cepat atau lambat menggantung proyek yang ditumbuhi. Untungnya, kita tidak berbicara tentang arsitektur "eksklusif" baru dengan singkatan yang megah. Saya harus mengakui bahwa arsitektur yang saat ini populer (MVP, MVVM, VIPER, Clean-swift) mengatasi tugas-tugas mereka, dan hanya pilihan yang salah dan penggunaan yang salah dari pendekatan ini atau itu yang dapat menyebabkan kesulitan. Namun, dalam kerangka arsitektur yang diadopsi, berbagai pola dapat digunakan, yang akan memungkinkan mencapai indikator yang hampir mitos tersebut: fleksibilitas, isolasi, testabilitas, penggunaan kembali.

Tentu saja, aplikasi berbeda. Jika suatu proyek hanya berisi beberapa layar yang terhubung secara seri, maka tidak ada kebutuhan khusus untuk interaksi yang kompleks antar modul. Hal ini dimungkinkan untuk dilakukan dengan koneksi segue biasa, bumbu semua ini dengan MVC / MVP lama yang baik. Dan meskipun keangkuhan arsitektur cepat atau lambat mengalahkan setiap pengembang, masih implementasi harus sepadan dengan tujuan dan kompleksitas proyek. Jadi, jika proyek melibatkan struktur layar yang kompleks dan berbagai status (otorisasi, mode tamu, offline, peran untuk pengguna, dll.), Maka pendekatan yang disederhanakan untuk arsitektur tentu akan memainkan trik: banyak dependensi, transfer data yang tidak jelas dan mahal antara layar dan status, masalah dengan navigasi dan yang paling penting - semua ini tidak akan memiliki fleksibilitas dan penggunaan kembali, solusi akan dicairkan dengan ketat ke dalam proyek dan layar A akan selalu membuka layar B. Upaya untuk membuat perubahan akan mengarah ke refaktor yang menyakitkan ngam selama itu sangat mudah untuk membuat kesalahan dan merusak apa yang dulu bekerja. Pada contoh di bawah ini, kami akan menjelaskan cara fleksibel untuk mengatur aplikasi yang memiliki dua status: pengguna tidak diotorisasi dan harus diarahkan ke layar otorisasi, pengguna diotorisasi dan layar Utama tertentu harus dibuka.

1. Implementasi protokol utama


Pertama kita perlu mengimplementasikan basis. Semuanya dimulai dengan protokol yang Dapat Dikoordinasikan, Dapat Datang, dan Dapat Dirute:

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

Dalam contoh ini, showAlert hanyalah metode yang mudah untuk memanggil notifikasi, yang ada dalam ekstensi 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. Buat koordinator


Dari waktu ke waktu, ada kebutuhan untuk mengubah layar aplikasi, yang berarti akan perlu untuk mengimplementasikan lapisan yang diuji tanpa downcast, serta tanpa melanggar prinsip-prinsip SOLID.

Kami melanjutkan ke implementasi layer koordinat:



Setelah memulai aplikasi, metode AppCoordinator harus dipanggil, yang menentukan aliran mana yang harus dimulai. Misalnya, jika pengguna terdaftar, maka Anda harus menjalankan aplikasi aliran, dan jika tidak, maka otorisasi mengalir. Dalam hal ini, Koordinator Utama dan Koordinator Otorisasi diperlukan. Kami akan menjelaskan koordinator untuk otorisasi, semua layar lain dapat dibuat dengan cara yang sama.

Pertama, Anda perlu menambahkan output ke koordinator sehingga ia dapat memiliki koneksi dengan koordinator yang lebih tinggi (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 } } 



Seperti yang ditunjukkan di atas, kami memiliki koordinator Otorisasi dengan router dan pabrik modul. Tetapi siapa dan kapan memanggil metode start ()?
Di sini kita perlu mengimplementasikan 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 } 

Dari contoh, Anda dapat melihat bahwa AppCoordinator memiliki router, pabrik koordinator, dan status titik masuk untuk AppCoordinator, yang perannya menentukan awal aliran aplikasi.

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

3. Implementasi koordinator pabrik


Masing-masing koordinator diinisialisasi dengan router dan pabrik modul. Selain itu, masing-masing koordinator harus mewarisi dari koordinator pangkalan:

 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 - kelas yang berisi larik koordinator anak dan dua metode: Hapus dan Tambahkan ketergantungan koordinator.

4. Mengkonfigurasi AppDelegate


Sekarang mari kita lihat seperti apa tampilan UIAplatformMain:

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

Segera setelah metode delegasi didFinishLaunchingWithOptions dipanggil, metode start () dari AppCoordinator dipanggil, yang akan menentukan logika aplikasi selanjutnya.

5. Membuat modul layar


Untuk menunjukkan apa yang terjadi selanjutnya, mari kita kembali ke AuthorizationCoordinator dan mengimplementasikan metode performFlow ().

Pertama, kita perlu mengimplementasikan antarmuka AuthorizationFactoryProtocol di kelas ModulesFactory:

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

Dengan memanggil metode apa pun di pabrik modul, sebagai aturan, kami bermaksud menginisialisasi ViewController dari storyboard, dan kemudian menautkan semua komponen yang diperlukan dari modul ini dalam arsitektur tertentu (MVP, MVVM, CleanSwift).

Setelah persiapan yang diperlukan, kita dapat menerapkan metode performFlow () dari AuthorizationCoordinator.
Layar mulai dalam koordinator ini adalah EnterView.
Dalam metode performFlow (), menggunakan pabrik modul, pembuatan modul yang sudah jadi untuk koordinator yang diberikan disebut, kemudian logika pemrosesan penutupan yang dipanggil oleh pengontrol tampilan kami pada satu waktu atau yang lain diimplementasikan, maka modul ini diatur oleh router sebagai root di tumpukan navigasi layar:

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




Terlepas dari kompleksitas yang tampak di beberapa tempat, pola ini sangat ideal untuk bekerja dengan file tiruan, memungkinkan Anda untuk sepenuhnya mengisolasi modul dari satu sama lain, dan juga mengabstraksi kami dari UIKit, yang sangat cocok untuk cakupan pengujian penuh. Pada saat yang sama, Koordinator tidak memaksakan persyaratan yang ketat pada arsitektur aplikasi dan hanya merupakan tambahan yang nyaman, penataan navigasi, dependensi dan aliran data antar modul.

Tautan ke github , yang berisi demo berdasarkan arsitektur Bersih dan Templat Xcode yang mudah digunakan untuk membuat lapisan arsitektur yang diperlukan.

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


All Articles