Pengembangan modular atau jalan ke sana, bukan kembali


Bagaimana kami sampai pada pendekatan baru untuk bekerja dengan modul dalam aplikasi iOS RaiffeisenBank.

Masalah


Dalam aplikasi Raiffeisenbank, setiap layar terdiri dari beberapa modul yang se independen mungkin dari satu sama lain. "Modul" kita sebut komponen visual yang memiliki idenya sendiri. Saat mendesain aplikasi, sangat penting untuk menulis logika sehingga modulnya independen dan mereka dapat dengan mudah ditambahkan atau dihapus tanpa menggunakan refactoring.

Kesulitan apa yang kami hadapi:


Menyorot abstraksi atas pola arsitektur
Sudah pada tahap pertama pengembangan, menjadi jelas bahwa kami tidak ingin terikat dengan pola arsitektur tertentu. MVC bagus jika Anda perlu menampilkan halaman dengan beberapa informasi. Pada saat yang sama, interaksi dengan pengguna minimal atau tidak sama sekali. Misalnya: halaman "tentang perusahaan" atau "perjanjian pengguna". VIPER adalah alat yang bagus untuk modul kompleks yang memiliki logika sendiri untuk bekerja dengan layanan, perutean, dan banyak hal lainnya.

Masalah interaksi dan enkapsulasi
Setiap pola arsitektur memiliki struktur konstruksi dan protokolnya sendiri, yang memberlakukan batasan untuk bekerja dengan modul. Untuk mengabstraksi modul, Anda perlu menyoroti antarmuka interaksi input / output utama.

Menyoroti logika routing
Modul sebagai unit visual tidak boleh dan tidak bisa tahu di mana dan bagaimana itu ditampilkan. Satu dan modul yang sama harus dan dapat diimplementasikan sebagai unit independen pada layar apa pun atau sebagai komposisi. Tanggung jawab untuk ini tidak dapat disalahkan pada modul itu sendiri.

Solusi sebelumnya: // Bisnis yang buruk


Solusi pertama yang kami tulis di Objective-C, dan didasarkan pada NSProxy. Masalah enkapsulasi pola arsitektur diselesaikan dengan defenisi, yang ditentukan oleh kondisi yang diberikan, yaitu input / output modul, yang memungkinkan untuk mem-proxy setiap panggilan ke modul untuk inputnya dan menerima pesan melalui output , jika ada.

Itu adalah langkah maju, tetapi kesulitan baru muncul:

  • Antarmuka proxy tidak menjamin implementasi protokol input ;
  • Keluaran harus dideskripsikan, bahkan jika itu tidak diperlukan;
  • Itu perlu untuk menambahkan properti output ke antarmuka input .

Selain NSProxy, kami juga menerapkan perutean dengan melihat gagasan ViperMcFlurry: kami membuat kategori pada ViewController , yang mulai tumbuh ketika opsi yang berbeda untuk menampilkan modul pada layar muncul. Tentu saja, kami membagi kategorinya, tetapi itu masih jauh dari solusi yang baik.

Secara umum ... pancake pertama kental, menjadi jelas bahwa Anda perlu menyelesaikan masalah secara berbeda.

Solusi: // Final


Menyadari bahwa tidak ada yang lebih jauh dengan NSProxy , kami mengambil spidol dan mulai menggambar. Akibatnya, kami mengisolasi protokol RFModule :

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

Kami sengaja meninggalkan tipe terkait di tingkat protokol, dan ada alasan bagus untuk ini: pada saat itu, 90% kode berada di Objective-C. Interoperabilitas antar modul ObjC ← → Swift tidak dimungkinkan.

Untuk tetap menggunakan obat generik dan memastikan penggunaan modul yang diketik, kami memperkenalkan kelas Modul yang memenuhi protokol
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} } } 

Jadi kami mendapat modul yang diketik. Dan pada kenyataannya, Swift menggunakan Modul kelas, dan dalam Objective-C RFModule . Selain itu, itu ternyata menjadi alat yang nyaman untuk menumbuk jenis di tempat Anda perlu membuat array: misalnya, TabContainer .

Karena DI untuk membuat modul berada dalam ruang lingkup UserStory, dan menetapkan nilai output di tempat di mana ia akan digunakan tidak dapat menggambarkan seter sederhana. "SetOutput" pada dasarnya adalah definisi yang, pada tahap pengalihan output, akan meneruskannya kepada orang yang bertanggung jawab, tergantung pada logika modul.

 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 

Transisi adalah protokol yang implementasinya, sesuai namanya, bertanggung jawab untuk logika menampilkan dan menyembunyikan modul.

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

Untuk tampilan ini disebabkan - perform , untuk hide - reverse . Terlepas dari kenyataan bahwa ada tujuan dalam protokol dan pada awalnya tampaknya ada sumbernya . Bahkan, sumber mungkin tidak, dan tipenya tidak selalu ViewController . Sebagai contoh, jika kita membutuhkan modul untuk membuka di jendela baru, ini adalah Jendela , dan jika kita perlu menanamkan , kita perlu DAN induk: ViewController DAN wadah: UIView .

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

Jadi, kami menyingkirkan ide untuk menulis ekstensi pada ViewController dan menjelaskan logika bagaimana kami menampilkan modul kami di berbagai objek. Ini memberi kami fleksibilitas dalam perutean, mis. sekarang kita dapat menampilkan modul apa saja baik secara independen maupun kompleks, dan juga bervariasi antara bagaimana semuanya ditampilkan di layar: di jendela, Present, di navigasi (push to navigation), sematkan, di tirai (penutup) .

Hanya itu semua


Ada satu hal lagi yang menghantui sejauh ini. Untuk kesempatan untuk dengan mudah memilih cara modul ditampilkan dan menghapus logika ini darinya, kami membayar hilangnya kemampuan untuk mengatur properti tampilan. Misalnya, jika kita memperlihatkannya di Navigasi, kita perlu menentukan warna barTintColor yang seharusnya; atau, jika kita menunjukkan modul di tirai, ada kebutuhan untuk mengatur warna pawang .

Sejauh ini, kami telah menyelesaikan masalah ini dengan tampilan yang tidak diketik: Properti apa pun, dan Transisi saat membuka modul mengarah ke jenis yang berfungsi, dan jika berhasil, ia menghilangkan properti yang diperlukan.

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


All Articles