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 arsitekturSudah 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 enkapsulasiSetiap 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 routingModul 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)?
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 }
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.