Perbandingan arsitektur Viper dan MVVM: Bagaimana cara menerapkan keduanya



Saat ini, VIPER dan MVVM adalah solusi arsitektur paling populer yang digunakan dalam pengembangan aplikasi besar yang membutuhkan partisipasi dalam pengembangan tim besar yang teruji dengan baik, didukung jangka panjang dan terus berkembang. Pada artikel ini kami akan mencoba menerapkannya pada proyek uji kecil, yang merupakan daftar kontak pengguna dengan kemampuan untuk menambahkan kontak baru. Artikel ini memiliki lebih banyak praktik daripada analitik, dan ditujukan terutama bagi mereka yang sudah secara teori akrab dengan arsitektur ini dan sekarang ingin memahami bagaimana ini bekerja dengan contoh-contoh spesifik. Namun, deskripsi dasar arsitektur dan perbandingannya juga ada.


Artikel ini adalah terjemahan dari artikel Rafael Sacchi “Membandingkan arsitektur MVVM dan Viper: Kapan harus menggunakan satu atau yang lain” . Sayangnya, pada beberapa titik dalam pembuatan artikel, "publikasi" telah dibuat alih-alih "terjemahan," jadi Anda harus menulis di sini.

Arsitektur yang dirancang dengan baik sangat penting untuk memastikan dukungan berkelanjutan untuk proyek Anda. Pada artikel ini, kita akan melihat arsitektur MVVM dan VIPER sebagai alternatif untuk MVC tradisional.

MVC adalah konsep terkenal untuk semua orang yang telah terlibat dalam pengembangan perangkat lunak selama beberapa waktu. Pola ini membagi proyek menjadi tiga bagian: Model mewakili entitas; Lihat, yang merupakan antarmuka untuk interaksi pengguna; dan Controller, bertanggung jawab untuk memastikan interaksi antara View dan Model. Ini adalah arsitektur yang Apple tawarkan untuk kita gunakan dalam aplikasi kita.

Namun, Anda mungkin tahu bahwa proyek datang dengan cukup banyak fungsi yang kompleks: dukungan untuk permintaan jaringan, penguraian, akses ke model data, konversi data untuk output, reaksi terhadap peristiwa antarmuka, dll. Sebagai hasilnya, Anda mendapatkan pengendali besar yang menyelesaikan tugas-tugas di atas dan banyak kode yang tidak dapat digunakan kembali. Dengan kata lain, MVC bisa menjadi mimpi buruk bagi pengembang dengan dukungan proyek jangka panjang. Tetapi bagaimana memastikan modularitas tinggi dan penggunaan kembali dalam proyek iOS?

Kita akan melihat dua alternatif yang sangat terkenal untuk arsitektur MVC: MVVM dan VIPER. Keduanya cukup terkenal di komunitas iOS dan telah membuktikan bahwa mereka bisa menjadi alternatif yang bagus untuk MVC. Kami akan berbicara tentang struktur mereka, menulis contoh aplikasi dan mempertimbangkan kasus ketika lebih baik menggunakan satu atau lain arsitektur.

Contoh

Kami akan menulis aplikasi dengan tabel kontak pengguna. Anda dapat menggunakan kode dari repositori ini . Di folder Starter, kerangka dasar proyek terkandung, dan di folder Final adalah aplikasi yang sudah selesai.

Aplikasi akan memiliki dua layar: pada yang pertama akan ada daftar kontak yang ditampilkan dalam sebuah tabel, di dalam sel akan ada nama pertama dan terakhir dari kontak, serta gambar dasar bukan gambar pengguna.



Layar kedua adalah layar untuk menambahkan kontak baru, dengan bidang input nama depan dan belakang dan tombol Selesai dan Batalkan.



MVVM

Cara kerjanya:

MVVM adalah singkatan dari Model-View-ViewModel . Pendekatan ini berbeda dari MVC dalam logika distribusi tanggung jawab antar modul.

  • Model : Modul ini tidak berbeda dari yang ada di MVC. Dia bertanggung jawab untuk membuat model data dan mungkin mengandung logika bisnis. Anda juga dapat membuat kelas pembantu, misalnya, seperti kelas manajer untuk mengelola objek di Model dan manajer jaringan untuk memproses permintaan dan penguraian jaringan.
  • Lihat : Dan di sini semuanya mulai berubah. Modul View di MVVM mencakup antarmuka (subclass dari file UIView, .xib dan .storyboard), logika tampilan (animasi, rendering) dan penanganan acara pengguna (penekanan tombol, dll.). View dan Controller bertanggung jawab atas hal ini dalam MVC. Ini berarti bahwa pandangan Anda akan tetap tidak berubah, sedangkan ViewController akan berisi sebagian kecil dari apa yang ada di dalamnya di MVC dan, karenanya, akan sangat berkurang.
  • ViewModel : Ini sekarang tempat di mana sebagian besar kode yang sebelumnya Anda miliki di ViewController akan ditemukan. Lapisan ViewModel meminta data dari Model (dapat berupa permintaan ke basis data lokal atau permintaan jaringan) dan mentransfernya kembali ke Lihat, dalam format yang akan digunakan dan ditampilkan di sana. Tapi ini adalah mekanisme dua arah, tindakan atau data yang dimasukkan oleh pengguna melewati ViewModel dan memperbarui Model. Karena ViewModel melacak segala sesuatu yang ditampilkan, ada baiknya menggunakan mekanisme penautan antara dua lapisan.


Dibandingkan dengan MVC, Anda bergerak dari arsitektur yang terlihat seperti ini:



Ke arsitektur berikutnya varant:



Di mana kelas dan subclass dari UIView dan UIViewController digunakan untuk mengimplementasikan View.

Nah, sekarang to the point. Mari kita menulis contoh aplikasi kita menggunakan arsitektur MVVM.

Aplikasi Kontak MVVM

MODEL

Kelas berikut adalah model kontak Kontak :

import CoreData open class Contact: NSManagedObject { @NSManaged var firstName: String? @NSManaged var lastName: String? var fullName: String { get { var name = "" if let firstName = firstName { name += firstName } if let lastName = lastName { name += " \(lastName)" } return name } } } 


Kelas kontak memiliki bidang firstName , lastName , serta properti nama lengkap dihitung.

LIHAT

LIHAT termasuk: storyboard utama, dengan pandangan sudah ditempatkan di atasnya; ContactsViewController, yang menampilkan daftar kontak dalam sebuah tabel; dan AddContactViewController dengan sepasang label dan bidang input untuk menambahkan nama dan nama keluarga dari kontak baru. Mari kita mulai dengan ContactsViewController . Kodenya akan terlihat seperti ini:

 import UIKit class ContactsViewController: UIViewController { @IBOutlet var tableView: UITableView! let contactViewModelController = ContactViewModelController() override func viewDidLoad() { super.viewDidLoad() tableView.tableFooterView = UIView() contactViewModelController.retrieveContacts({ [unowned self] in self.tableView.reloadData() }, failure: nil) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { let addContactNavigationController = segue.destination as? UINavigationController let addContactVC = addContactNavigationController?.viewControllers[0] as? AddContactViewController addContactVC?.contactsViewModelController = contactViewModelController addContactVC?.didAddContact = { [unowned self] (contactViewModel, index) in let indexPath = IndexPath(row: index, section: 0) self.tableView.beginUpdates() self.tableView.insertRows(at: [indexPath], with: .left) self.tableView.endUpdates() } } } extension ContactsViewController: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell") as? ContactsTableViewCell guard let contactsCell = cell else { return UITableViewCell() } contactsCell.cellModel = contactViewModelController.viewModel(at: (indexPath as NSIndexPath).row) return contactsCell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return contactViewModelController.contactsCount } } 


Bahkan dengan pandangan sekilas, jelas bahwa kelas ini mengimplementasikan sebagian besar tugas antarmuka. Ini juga memiliki navigasi dalam metode prepForSegue (: :) - dan inilah saat yang akan berubah dalam VIPER saat menambahkan lapisan Router.

Mari kita lihat lebih dekat pada ekstensi kelas yang mengimplementasikan protokol UITableViewDataSource. Fungsi tidak bekerja secara langsung dengan model kontak pengguna Kontak di lapisan Model - sebagai gantinya, mereka menerima data (diwakili oleh struktur ContactViewModel) dalam bentuk di mana mereka akan ditampilkan, sudah diformat menggunakan ViewModelController.

Hal yang sama terjadi di sirkuit, yang dimulai segera setelah membuat kontak. Satu-satunya tugasnya adalah menambahkan baris ke tabel dan memperbarui antarmuka.

Sekarang Anda perlu membangun hubungan antara subkelas dari UITableViewCell dan ViewModel. Ini akan terlihat seperti kelas sel dari tabel ContactsTableViewCell :

 import UIKit class ContactsTableViewCell: UITableViewCell { var cellModel: ContactViewModel? { didSet { bindViewModel() } } func bindViewModel() { textLabel?.text = cellModel?.fullName } } 


Dan begitu juga kelas AddContactViewController :

 import UIKit class AddContactViewController: UIViewController { @IBOutlet var firstNameTextField: UITextField! @IBOutlet var lastNameTextField: UITextField! var contactsViewModelController: ContactViewModelController? var didAddContact: ((ContactViewModel, Int) -> Void)? override func viewDidLoad() { super.viewDidLoad() firstNameTextField.becomeFirstResponder() } @IBAction func didClickOnDoneButton(_ sender: UIBarButtonItem) { guard let firstName = firstNameTextField.text, let lastName = lastNameTextField.text else { return } if firstName.isEmpty || lastName.isEmpty { showEmptyNameAlert() return } dismiss(animated: true) { [unowned self] in self.contactsViewModelController?.createContact(firstName: firstName, lastName: lastName, success: self.didAddContact, failure: nil) } } @IBAction func didClickOnCancelButton(_ sender: UIBarButtonItem) { dismiss(animated: true, completion: nil) } fileprivate func showEmptyNameAlert() { showMessage(title: "Error", message: "A contact must have first and last names") } fileprivate func showMessage(title: String, message: String) { let alertView = UIAlertController(title: title, message: message, preferredStyle: .alert) alertView.addAction(UIAlertAction(title: "Ok", style: .destructive, handler: nil)) present(alertView, animated: true, completion: nil) } } 


Dan lagi, terutama bekerja dengan UI sedang terjadi di sini. Perhatikan bahwa AddContactViewController mendelegasikan fungsionalitas pembuatan kontak ke ViewModelController di fungsi didClickOnDoneButton (:) .

LIHAT MODEL

Sudah waktunya untuk berbicara tentang lapisan ViewModel yang sama sekali baru bagi kita. Pertama, buat kelas kontak ContactViewModel yang akan memberikan tampilan yang perlu kita tampilkan, dan fungsi <and> dengan parameter akan ditentukan untuk mengurutkan kontak:

 public struct ContactViewModel { var fullName: String } public func <(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { return lhs.fullName.lowercased() < rhs.fullName.lowercased() } public func >(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { return lhs.fullName.lowercased() > rhs.fullName.lowercased() } 


Kode ContactViewModelController akan terlihat seperti ini:

 class ContactViewModelController { fileprivate var contactViewModelList: [ContactViewModel] = [] fileprivate var dataManager = ContactLocalDataManager() var contactsCount: Int { return contactViewModelList.count } func retrieveContacts(_ success: (() -> Void)?, failure: (() -> Void)?) { do { let contacts = try dataManager.retrieveContactList() contactViewModelList = contacts.map() { ContactViewModel(fullName: $0.fullName) } success?() } catch { failure?() } } func viewModel(at index: Int) -> ContactViewModel { return contactViewModelList[index] } func createContact(firstName: String, lastName: String, success: ((ContactViewModel, Int) -> Void)?, failure: (() -> Void)?) { do { let contact = try dataManager.createContact(firstName: firstName, lastName: lastName) let contactViewModel = ContactViewModel(fullName: contact.fullName) let insertionIndex = contactViewModelList.insertionIndex(of: contactViewModel) { $0 < $1 } contactViewModelList.insert(contactViewModel, at: insertionIndex) success?(contactViewModel, insertionIndex) } catch { failure?() } } } 


Catatan: MVVM tidak memberikan definisi yang tepat tentang cara membuat ViewModel. Ketika saya ingin membuat arsitektur yang lebih berlapis, saya lebih suka membuat ViewModelController yang akan berinteraksi dengan lapisan Model dan akan bertanggung jawab untuk membuat objek ViewModel.

Hal utama yang sangat mudah diingat: lapisan ViewModel tidak boleh terlibat dalam bekerja dengan antarmuka pengguna. Untuk menghindari ini, lebih baik tidak pernah mengimpor UIKit ke file dengan ViewModel.

Kelas ContactViewModelController meminta kontak dari penyimpanan lokal dan mencoba untuk tidak mempengaruhi lapisan Model. Ini mengembalikan data dalam format yang tampilan harus ditampilkan, dan memberitahukan tampilan ketika kontak baru ditambahkan dan data berubah.

Dalam kehidupan nyata, ini akan menjadi permintaan jaringan, dan bukan permintaan ke database lokal, tetapi dalam kasus apa pun mereka tidak boleh menjadi bagian dari ViewModel - dan bekerja dengan jaringan dan bekerja dengan database lokal harus disediakan menggunakan manajer mereka sendiri ( manajer).

Itu semua tentang MVVM. Mungkin pendekatan ini menurut Anda lebih dapat diuji, didukung, dan didistribusikan daripada MVC. Sekarang mari kita bicara tentang VIPER dan lihat perbedaannya dengan MVVM.

VIPER

Cara kerjanya:

VIPER adalah implementasi Arsitektur Bersih untuk proyek iOS. Strukturnya terdiri dari: View, Interactor, Presenter, Entity, dan Router. Ini benar-benar arsitektur yang sangat terdistribusi dan modular yang memungkinkan Anda untuk berbagi tanggung jawab, dicakup dengan baik oleh unit test dan membuat kode Anda dapat digunakan kembali.

  • Lihat : Lapisan antarmuka yang biasanya menyiratkan file UIKit (termasuk UIViewController). Dapat dipahami bahwa dalam sistem yang lebih terdistribusi, subclass dari UIViewController harus terkait dengan View. Dalam VIPER, segalanya hampir sama dengan di MVVM: View bertanggung jawab untuk menampilkan apa yang Presenter berikan dan untuk mentransmisikan informasi atau tindakan yang dimasukkan pengguna ke Presenter.
  • Interactor : Berisi logika bisnis yang diperlukan agar aplikasi dapat berfungsi. Interactor bertanggung jawab untuk mengambil data dari Model (permintaan jaringan atau lokal) dan implementasinya sama sekali tidak terkait dengan antarmuka pengguna. Penting untuk diingat bahwa manajer jaringan dan lokal bukan bagian dari VIPER, tetapi diperlakukan sebagai dependensi terpisah.
  • Presenter : Bertanggung jawab untuk memformat data untuk ditampilkan di View. Dalam MVVM dalam contoh kita, ViewModelController bertanggung jawab untuk ini. Presenter menerima data dari Interactor, membuat instance ViewModel (kelas yang diformat untuk tampilan yang benar) dan meneruskannya ke View. Dia juga menanggapi input data pengguna, meminta data tambahan dari database, atau sebaliknya, memberikannya padanya.
  • Entitas : Mengambil bagian dari tanggung jawab lapisan Model, yang digunakan dalam arsitektur lain. Entity adalah objek data sederhana, tanpa logika bisnis, yang dikelola oleh traktor online dan berbagai manajer data.
  • Router : Semua logika navigasi aplikasi. Tampaknya ini bukan lapisan yang paling penting, tetapi jika Anda perlu, misalnya, untuk menggunakan kembali tampilan yang sama pada iPhone dan aplikasi untuk iPad, satu-satunya hal yang dapat berubah adalah bagaimana tampilan Anda muncul di layar. Ini memungkinkan Anda untuk tidak menyentuh lapisan lagi kecuali Router, yang akan bertanggung jawab untuk ini dalam setiap kasus.


Dibandingkan dengan MVVM, VIPER memiliki beberapa perbedaan utama dalam distribusi tanggung jawab:

- dia memiliki Router, layer terpisah yang bertanggung jawab untuk navigasi

- Entitas adalah objek data sederhana, mendistribusikan kembali tanggung jawab untuk mengakses data dari Model ke Interactor

- Tanggung jawab ViewModelController dibagi antara Interactor dan Presenter

Dan sekarang mari kita ulangi aplikasi yang sama, tetapi sudah di VIPER. Namun untuk memudahkan pemahaman, kami hanya akan membuat pengontrol dengan kontak. Anda dapat menemukan kode untuk controller untuk menambahkan kontak baru di proyek menggunakan tautan (folder Starter Kontak VIPER dalam repositori ini ).

Catatan : Jika Anda memutuskan untuk membuat proyek Anda pada VIPER, maka Anda sebaiknya tidak mencoba membuat semua file secara manual - Anda dapat menggunakan salah satu generator kode, misalnya, seperti VIPER Gen atau Generamba (proyek Rambler) .

Aplikasi Kontak VIPER

LIHAT

LIHAT diwakili oleh elemen-elemen dari Main.storyboard dan kelas ContactListView. LIHAT sangat pasif; satu-satunya tugasnya adalah mentransfer acara antarmuka ke Presenter dan memperbarui kondisinya, setelah pemberitahuan dari Presenter. Seperti inilah kode ContactListView :

 import UIKit class ContactListView: UIViewController { @IBOutlet var tableView: UITableView! var presenter: ContactListPresenterProtocol? var contactList: [ContactViewModel] = [] override func viewDidLoad() { super.viewDidLoad() presenter?.viewDidLoad() tableView.tableFooterView = UIView() } @IBAction func didClickOnAddButton(_ sender: UIBarButtonItem) { presenter?.addNewContact(from: self) } } extension ContactListView: ContactListViewProtocol { func reloadInterface(with contacts: [ContactViewModel]) { contactList = contacts tableView.reloadData() } func didInsertContact(_ contact: ContactViewModel) { let insertionIndex = contactList.insertionIndex(of: contact) { $0 < $1 } contactList.insert(contact, at: insertionIndex) let indexPath = IndexPath(row: insertionIndex, section: 0) tableView.beginUpdates() tableView.insertRows(at: [indexPath], with: .right) tableView.endUpdates() } } extension ContactListView: UITableViewDataSource { func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "ContactCell") else { return UITableViewCell() } cell.textLabel?.text = contactList[(indexPath as NSIndexPath).row].fullName return cell } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return contactList.count } } 


Lihat mengirimkan viewDidLoad dan peristiwa didClickOnAddButton ke Presenter. Pada acara pertama, Presenter akan meminta data dari Interactor, dan pada yang kedua, Presenter akan meminta Router untuk beralih ke controller untuk menambahkan kontak baru.

Metode protokol ContactListViewProtocol dipanggil dari Presenter baik ketika daftar kontak diminta, atau ketika kontak baru ditambahkan. Dalam kedua kasus tersebut, data dalam Tampilan hanya berisi informasi yang diperlukan untuk ditampilkan.

Juga di Lihat adalah metode yang mengimplementasikan protokol UITableViewDataSource yang mengisi tabel dengan data yang diterima.

INTERAKTOR

Interactor dalam contoh kita cukup sederhana. Semua yang dia lakukan adalah meminta data melalui manajer basis data lokal, dan tidak masalah baginya apa yang digunakan manajer ini, CoreData, Realm, atau solusi lainnya. Kode dalam ContactListInteractor adalah sebagai berikut:

 class ContactListInteractor: ContactListInteractorInputProtocol { weak var presenter: ContactListInteractorOutputProtocol? var localDatamanager: ContactListLocalDataManagerInputProtocol? func retrieveContacts() { do { if let contactList = try localDatamanager?.retrieveContactList() { presenter?.didRetrieveContacts(contactList) } else { presenter?.didRetrieveContacts([]) } } catch { presenter?.didRetrieveContacts([]) } } } 


Setelah Interactor menerima data yang diminta, ia memberi tahu Presenter. Selain itu, sebagai opsi, Interactor dapat mengirimkan kesalahan ke Presenter, yang kemudian harus memformat kesalahan menjadi tampilan yang sesuai untuk ditampilkan di Tampilan.

Catatan : Seperti yang mungkin Anda perhatikan, setiap lapisan dalam VIPER mengimplementasikan protokol. Akibatnya, kelas tergantung pada abstraksi, dan bukan pada implementasi tertentu, sehingga memenuhi prinsip inversi ketergantungan (salah satu prinsip SOLID).

PRESENTER

Elemen terpenting arsitektur. Semua komunikasi antara View dan seluruh layer (Interactor dan Router) melewati Presenter. Kode Daftar Kontak :

 class ContactListPresenter: ContactListPresenterProtocol { weak var view: ContactListViewProtocol? var interactor: ContactListInteractorInputProtocol? var wireFrame: ContactListWireFrameProtocol? func viewDidLoad() { interactor?.retrieveContacts() } func addNewContact(from view: ContactListViewProtocol) { wireFrame?.presentAddContactScreen(from: view) } } extension ContactListPresenter: ContactListInteractorOutputProtocol { func didRetrieveContacts(_ contacts: [Contact]) { view?.reloadInterface(with: contacts.map() { return ContactViewModel(fullName: $0.fullName) }) } } extension ContactListPresenter: AddModuleDelegate { func didAddContact(_ contact: Contact) { let contactViewModel = ContactViewModel(fullName: contact.fullName) view?.didInsertContact(contactViewModel) } func didCancelAddContact() {} } 


Setelah View dimuat, ia memberi tahu Presenter, yang pada gilirannya meminta data melalui Interactor. Saat pengguna mengklik tombol tambahkan kontak baru, View memberi tahu Presenter, yang mengirimkan permintaan untuk membuka layar tambahkan kontak baru di Router.

Presenter juga memformat data dan mengembalikannya ke tampilan setelah menanyakan daftar kontak. Dia juga bertanggung jawab untuk mengimplementasikan protokol AddModuleDelegate. Ini berarti Presenter akan menerima notifikasi ketika kontak baru ditambahkan, menyiapkan data kontak untuk ditampilkan dan mentransfer ke View.

Seperti yang mungkin telah Anda perhatikan, Presenter memiliki setiap kesempatan untuk menjadi cukup rumit. Jika ada kemungkinan seperti itu, maka Presenter dapat dibagi menjadi dua bagian: Presenter, yang hanya menerima data, memformatnya untuk ditampilkan, dan meneruskannya ke View; dan pengendali acara yang akan merespons tindakan pengguna.

ENTITY

Lapisan ini mirip dengan lapisan Model di MVVM. Dalam aplikasi kami, ini diwakili oleh kelas Kontak dan fungsi definisi operator <and>. Konten kontak akan terlihat seperti ini:

 import CoreData open class Contact: NSManagedObject { var fullName: String { get { var name = "" if let firstName = firstName { name += firstName } if let lastName = lastName { name += " " + lastName } return name } } } public struct ContactViewModel { var fullName = "" } public func <(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { return lhs.fullName.lowercased() < rhs.fullName.lowercased() } public func >(lhs: ContactViewModel, rhs: ContactViewModel) -> Bool { return lhs.fullName.lowercased() > rhs.fullName.lowercased() } 


ContactViewModel berisi bidang yang diisi oleh Presenter (format) yang ditampilkan oleh Tampilan. Kelas Kontak adalah subkelas NSManagedObject yang berisi bidang yang sama seperti pada model CoreData.

ROUTER

Dan akhirnya, yang terakhir, tetapi tentu saja tidak penting, lapisan. Semua tanggung jawab untuk navigasi ada pada Presenter dan WireFrame. Presenter menerima suatu peristiwa dari pengguna dan tahu kapan harus melakukan transisi, dan WireFrame tahu bagaimana dan di mana membuat transisi ini. Agar Anda tidak bingung, dalam contoh ini layer Router diwakili oleh kelas ContactListWireFrame dan disebut sebagai WireFrame dalam teks. ContactListWireFrame Code:

 import UIKit class ContactListWireFrame: ContactListWireFrameProtocol { class func createContactListModule() -> UIViewController { let navController = mainStoryboard.instantiateViewController(withIdentifier: "ContactsNavigationController") if let view = navController.childViewControllers.first as? ContactListView { let presenter: ContactListPresenterProtocol & ContactListInteractorOutputProtocol = ContactListPresenter() let interactor: ContactListInteractorInputProtocol = ContactListInteractor() let localDataManager: ContactListLocalDataManagerInputProtocol = ContactListLocalDataManager() let wireFrame: ContactListWireFrameProtocol = ContactListWireFrame() view.presenter = presenter presenter.view = view presenter.wireFrame = wireFrame presenter.interactor = interactor interactor.presenter = presenter interactor.localDatamanager = localDataManager return navController } return UIViewController() } static var mainStoryboard: UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) } func presentAddContactScreen(from view: ContactListViewProtocol) { guard let delegate = view.presenter as? AddModuleDelegate else { return } let addContactsView = AddContactWireFrame.createAddContactModule(with: delegate) if let sourceView = view as? UIViewController { sourceView.present(addContactsView, animated: true, completion: nil) } } } 


Karena WireFrame bertanggung jawab untuk membuat modul, akan lebih mudah untuk mengkonfigurasi semua dependensi di sini. Saat Anda ingin membuka pengontrol lain, fungsi yang membuka pengontrol baru menerima sebagai argumen objek yang akan membukanya, dan membuat pengontrol baru menggunakan WireFrame-nya. Juga, ketika membuat controller baru, data yang diperlukan ditransfer ke sana, dalam hal ini hanya delegasi (Presenter controller dengan kontak) untuk menerima kontak yang dibuat.

Lapisan Router memberikan peluang bagus untuk menghindari penggunaan segue (transisi) di storyboard dan mengatur semua navigasi kode. Karena storyboard tidak memberikan solusi ringkas untuk mentransfer data antara pengontrol, implementasi navigasi kami tidak akan menambahkan kode tambahan. Yang kami dapatkan hanyalah penggunaan ulang terbaik.


Ringkasan :

Anda dapat menemukan kedua proyek di repositori ini .

Seperti yang Anda lihat, MVVM dan VIPER, meskipun berbeda, tidak unik. MVVM memberi tahu kita bahwa selain View dan Model, harus ada juga layer ViewModel. Tetapi tidak ada yang dikatakan tentang bagaimana lapisan ini harus dibuat, atau tentang bagaimana data diminta - tanggung jawab untuk lapisan ini tidak didefinisikan dengan jelas. Ada banyak cara untuk mengimplementasikannya dan Anda dapat menggunakannya.

VIPER, di sisi lain, adalah arsitektur yang agak unik. Ini terdiri dari banyak lapisan, yang masing-masing memiliki area tanggung jawab yang jelas dan kurang dari MVVM dipengaruhi oleh pengembang.

Ketika memilih arsitektur, biasanya tidak ada satu-satunya solusi yang tepat, tetapi saya masih akan mencoba memberikan beberapa tips. Jika Anda memiliki proyek besar dan panjang, dengan persyaratan yang jelas dan Anda ingin memiliki banyak kesempatan untuk menggunakan kembali komponen, maka VIPER akan menjadi solusi terbaik. Penggambaran tanggung jawab yang lebih jelas memungkinkan dilakukannya pengujian yang lebih baik dan peningkatan penggunaan kembali.

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


All Articles