
वर्तमान में, VIPER और MVVM बड़े अनुप्रयोगों के विकास में उपयोग किए जाने वाले सबसे लोकप्रिय वास्तुशिल्प समाधान हैं जो बड़ी टीमों के विकास में भागीदारी की आवश्यकता होती है जो अच्छी तरह से परीक्षण, दीर्घकालिक समर्थित और लगातार विकसित हो रहे हैं। इस लेख में हम उन्हें एक छोटे परीक्षण प्रोजेक्ट पर लागू करने का प्रयास करेंगे, जो एक नए संपर्क को जोड़ने की क्षमता के साथ उपयोगकर्ता संपर्कों की एक सूची है। इस लेख में विश्लेषिकी की तुलना में अधिक अभ्यास है, और यह मुख्य रूप से उन लोगों के लिए है जो पहले से ही इन आर्किटेक्चर से परिचित हैं और अब यह समझना चाहते हैं कि यह विशिष्ट उदाहरणों के साथ कैसे काम करता है। हालांकि, आर्किटेक्चर और उनकी तुलना का एक मूल विवरण भी मौजूद है।
यह लेख राफेल साकची
के लेख
"एमवीवीएम और वाइपर आर्किटेक्चर की तुलना: जब एक या दूसरे का उपयोग करने के लिए" का अनुवाद है । दुर्भाग्य से, लेख के निर्माण में कुछ बिंदु पर, "अनुवाद" के बजाय "प्रकाशन" स्थापित किया गया था, इसलिए आपको यहां लिखना होगा।
आपकी परियोजना के लिए निरंतर समर्थन सुनिश्चित करने के लिए एक अच्छी तरह से डिजाइन की गई वास्तुकला आवश्यक है। इस लेख में, हम MVVM और VIPER आर्किटेक्चर को पारंपरिक MVC के विकल्प के रूप में देखेंगे।
एमवीसी उन सभी के लिए एक प्रसिद्ध अवधारणा है जो काफी समय से सॉफ्टवेयर विकास में शामिल हैं। यह पैटर्न परियोजना को तीन भागों में विभाजित करता है: मॉडल का प्रतिनिधित्व करने वाली संस्थाएं; देखें, जो उपयोगकर्ता इंटरैक्शन के लिए एक इंटरफ़ेस है; और नियंत्रक, व्यू और मॉडल के बीच बातचीत सुनिश्चित करने के लिए जिम्मेदार है। यह वह वास्तुकला है जो Apple हमें अपने अनुप्रयोगों में उपयोग करने के लिए प्रदान करता है।
हालांकि, आप शायद जानते हैं कि परियोजनाएं काफी जटिल कार्यक्षमता के साथ आती हैं: नेटवर्क अनुरोधों के लिए समर्थन, पार्सिंग, डेटा मॉडल तक पहुंच, आउटपुट के लिए डेटा परिवर्तित करना, इंटरफ़ेस घटनाओं की प्रतिक्रिया आदि। नतीजतन, आपको विशाल नियंत्रक मिलते हैं जो उपरोक्त कार्यों को हल करते हैं और कोड का एक गुच्छा जो पुन: उपयोग नहीं किया जा सकता है। दूसरे शब्दों में, एमवीसी एक डेवलपर के लिए दीर्घकालिक परियोजना समर्थन के लिए एक बुरा सपना हो सकता है। लेकिन आईओएस परियोजनाओं में उच्च मॉड्यूलरिटी और पुन: प्रयोज्यता कैसे सुनिश्चित करें?
हम एमवीसी वास्तुकला और वीवीईआर: एमवीसी वास्तुकला के लिए दो बहुत प्रसिद्ध विकल्पों पर गौर करेंगे। ये दोनों ही आईओएस समुदाय में काफी प्रसिद्ध हैं और उन्होंने साबित किया है कि वे एमवीसी के लिए एक बढ़िया विकल्प हो सकते हैं। हम उनकी संरचना के बारे में बात करेंगे, एक उदाहरण आवेदन लिखेंगे और उन मामलों पर विचार करेंगे जब एक या किसी अन्य वास्तुकला का उपयोग करना बेहतर होता है।
उदाहरणहम उपयोगकर्ता संपर्कों की एक तालिका के साथ एक आवेदन लिखेंगे। आप
इस रिपॉजिटरी से कोड का उपयोग कर सकते हैं। स्टार्टर फ़ोल्डरों में, प्रोजेक्ट का मूल कंकाल निहित है, और अंतिम फ़ोल्डरों में एक पूरी तरह से तैयार एप्लिकेशन है।
एप्लिकेशन में दो स्क्रीन होंगे: पहले में टेबल के रूप में प्रदर्शित संपर्कों की एक सूची होगी, सेल में संपर्क का पहला और अंतिम नाम होगा, साथ ही उपयोगकर्ता की छवि के बजाय आधार चित्र होगा।

दूसरी स्क्रीन एक नया संपर्क जोड़ने के लिए स्क्रीन है, जिसमें पहले और अंतिम नाम इनपुट फ़ील्ड और ड्रोन और रद्द करें बटन हैं।
MVVMयह कैसे काम करता है:
MVVM का अर्थ है
मॉडल-व्यू-व्यूमॉडल । मॉड्यूल के बीच जिम्मेदारी वितरण के तर्क में यह दृष्टिकोण MVC से भिन्न है।
- मॉडल : यह मॉड्यूल MVC में इससे अलग नहीं है। वह डेटा मॉडल बनाने के लिए जिम्मेदार है और इसमें व्यावसायिक तर्क हो सकते हैं। आप सहायक वर्ग भी बना सकते हैं, उदाहरण के लिए, नेटवर्क अनुरोधों को संसाधित करने और पार्स करने के लिए मॉडल और नेटवर्क प्रबंधक में वस्तुओं के प्रबंधन के लिए एक प्रबंधक वर्ग।
- देखें : और यहां सब कुछ बदलना शुरू हो जाता है। MVVM में व्यू मॉड्यूल इंटरफ़ेस को कवर करता है (UIView, .xib और .storyboard फ़ाइलों के उपवर्ग), प्रदर्शन तर्क (एनीमेशन, प्रतिपादन) और उपयोगकर्ता की घटनाओं से निपटने (बटन क्लिक आदि) MVC में, देखें और नियंत्रक इसके लिए जिम्मेदार हैं। इसका मतलब यह है कि आपके पास मौजूद विचार अपरिवर्तित रहेंगे, जबकि ViewController में MVC में जो कुछ था, उसका एक छोटा हिस्सा होगा और तदनुसार, बहुत कम हो जाएगा।
- ViewModel : यह अब वह जगह है, जहां आपके द्वारा पहले ViewController में रखा गया अधिकांश कोड स्थित होगा। ViewModel परत मॉडल से डेटा का अनुरोध करता है (यह स्थानीय डेटाबेस या नेटवर्क अनुरोध के लिए एक अनुरोध हो सकता है) और इसे वापस व्यू में स्थानांतरित करता है, जिस प्रारूप में इसका उपयोग किया जाएगा और वहां प्रदर्शित किया जाएगा। लेकिन यह एक द्वि-दिशात्मक तंत्र है, उपयोगकर्ता द्वारा दर्ज किए गए कार्य या डेटा ViewModel से गुजरते हैं और मॉडल को अपडेट करते हैं। चूंकि ViewModel उन सभी चीजों का ट्रैक रखता है, जो दो परतों के बीच लिंकिंग तंत्र का उपयोग करने के लिए उपयोगी है।
एमवीसी की तुलना में, आप एक वास्तुकला से आगे बढ़ रहे हैं जो इस तरह दिखता है:

अगले आर्किटेक्चर संस्करण के लिए:

जिसमें व्यू को लागू करने के लिए यूआईवाइ और यूआईवाइकॉन्ट्रोलर की कक्षाओं और उपवर्गों का उपयोग किया जाता है।
खैर, अब बात है। आइए MVVM आर्किटेक्चर का उपयोग करके हमारे एप्लिकेशन का एक उदाहरण लिखते हैं।
MVVM संपर्क ऐपमॉडलनिम्नलिखित वर्ग एक
संपर्क संपर्क मॉडल है:
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 } } }
संपर्क वर्ग में फ़ील्ड्स पहले
नाम , अंतिम
नाम , साथ ही गणना
पूर्ण नाम संपत्ति है।
देखेंदृश्य में शामिल हैं: मुख्य स्टोरीबोर्ड, पहले से ही उस पर रखे गए विचारों के साथ; संपर्क दृश्य नियंत्रक, जो एक तालिका में संपर्कों की सूची प्रदर्शित करता है; और AddContactViewController नए संपर्क का नाम और उपनाम जोड़ने के लिए लेबल और इनपुट फ़ील्ड की एक जोड़ी के साथ। चलिए
ContactsViewController से शुरू करते हैं। इसका कोड इस तरह दिखेगा:
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 } }
सरसरी नज़र से भी, यह स्पष्ट है कि यह वर्ग सबसे अधिक भाग इंटरफ़ेस कार्यों के लिए लागू करता है। यह भी
तैयारी में नेविगेशन है।
इसमें (: :) विधि - और यह बिल्कुल ऐसा क्षण है जो राउटर की परत को जोड़ने पर VIPER में बदल जाएगा।
आइए UITableViewDataSource प्रोटोकॉल को लागू करने वाले वर्ग एक्सटेंशन पर एक करीब से नज़र डालें। मॉडल परत में संपर्क उपयोगकर्ता के संपर्क मॉडल के साथ फ़ंक्शंस सीधे काम नहीं करते हैं - इसके बजाय, वे डेटा (ContactViewModel संरचना द्वारा प्रतिनिधित्व) को उस रूप में प्राप्त करते हैं जिसमें वे प्रदर्शित होंगे, पहले से ही ViewModelController का उपयोग करके स्वरूपित किया गया है।
एक सर्किट में एक ही चीज होती है, जो संपर्क बनाने के तुरंत बाद शुरू होती है। उनका एकमात्र कार्य तालिका में एक पंक्ति जोड़ना और इंटरफ़ेस को अद्यतन करना है।
अब आपको UITableViewCell और ViewModel के उपवर्ग के बीच संबंध स्थापित करने की आवश्यकता है। यह
संपर्क तालिका के सेल वर्ग की तरह दिखेगा:
import UIKit class ContactsTableViewCell: UITableViewCell { var cellModel: ContactViewModel? { didSet { bindViewModel() } } func bindViewModel() { textLabel?.text = cellModel?.fullName } }
और इसलिए
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) } }
और फिर, मुख्य रूप से यूआई के साथ काम यहां चल रहा है। ध्यान दें कि AddContactViewController,
DidClickOnDoneButton (:) फ़ंक्शन में ViewModelController के लिए संपर्क निर्माण कार्यक्षमता को
दर्शाता है ।
देखें मॉडलयह हमारे लिए पूरी तरह से नई ViewModel परत के बारे में बात करने का समय है। सबसे पहले, एक
ContactViewModel संपर्क
वर्ग बनाएं, जो दृश्य को हमें प्रदर्शित करने की आवश्यकता प्रदान करेगा, और मापदंडों के साथ <और> कार्यों को छँटाई संपर्कों के लिए परिभाषित किया जाएगा:
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() }
ContactViewModelController कोड इस तरह दिखेगा:
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?() } } }
नोट: MVVM ViewModel बनाने की सही परिभाषा नहीं देता है। जब मैं अधिक स्तरित वास्तुकला बनाना चाहता हूं, तो मैं एक ViewModelController बनाना पसंद करता हूं जो मॉडल परत के साथ बातचीत करेगा और ViewModel ऑब्जेक्ट बनाने के लिए जिम्मेदार होगा।
मुख्य बात जो याद रखना बहुत आसान है: ViewModel परत को उपयोगकर्ता इंटरफ़ेस के साथ काम करने में शामिल नहीं होना चाहिए। इससे बचने के लिए, यह बेहतर है कि
कभी भी ViewModel के साथ एक फ़ाइल में UIKit आयात
न करें ।
ContactViewModelController वर्ग स्थानीय संग्रहण से संपर्क का अनुरोध करता है और मॉडल परत को प्रभावित नहीं करने की कोशिश करता है। यह उस प्रारूप में डेटा लौटाता है जिसे देखने के लिए दृश्य की आवश्यकता होती है, और जब नया संपर्क जोड़ा जाता है और डेटा बदल जाता है तो दृश्य को सूचित करता है।
वास्तविक जीवन में, यह एक नेटवर्क अनुरोध होगा, और स्थानीय डेटाबेस के लिए अनुरोध नहीं होगा, लेकिन किसी भी स्थिति में उनमें से कोई भी ViewModel का हिस्सा नहीं होना चाहिए - और नेटवर्क के साथ काम करना चाहिए और स्थानीय डेटाबेस के साथ काम अपने स्वयं के प्रबंधकों का उपयोग करके प्रदान किया जाना चाहिए ( प्रबंधकों)।
यह सब MVVM के बारे में है। शायद यह दृष्टिकोण आपको MVC की तुलना में अधिक परीक्षण योग्य, समर्थित और वितरित प्रतीत होगा। अब VIPER के बारे में बात करते हैं और देखते हैं कि यह MVVM से कैसे भिन्न है।
सांपयह कैसे काम करता है:
VIPER iOS प्रोजेक्ट्स के लिए एक क्लीन आर्किटेक्चर कार्यान्वयन है। इसकी संरचना में शामिल हैं: व्यू, इंटरेक्टर, प्रस्तोता, इकाई और रूटर। यह वास्तव में एक बहुत ही वितरित और मॉड्यूलर वास्तुकला है जो आपको जिम्मेदारी साझा करने की अनुमति देता है, यूनिट परीक्षणों द्वारा बहुत अच्छी तरह से कवर किया गया है और आपके कोड को पुन: प्रयोज्य बनाता है।
- देखें : एक इंटरफ़ेस परत जो आमतौर पर UIKit फ़ाइलों (UIViewController सहित) का अर्थ है। यह समझा जा सकता है कि अधिक वितरित प्रणालियों में, UIViewController के उपवर्ग दृश्य से संबंधित होने चाहिए। VIPER में, चीज़ें लगभग MVVM की तरह ही हैं: View प्रस्तुतकर्ता को प्रस्तुत करने और उपयोगकर्ता द्वारा दर्ज की गई जानकारी या क्रियाओं को प्रस्तुत करने के लिए प्रस्तुत करने के लिए ज़िम्मेदार है।
- इंटरेक्टर : एप्लिकेशन के काम करने के लिए आवश्यक व्यावसायिक तर्क समाहित करता है। इंटरएक्टर मॉडल (नेटवर्क या स्थानीय अनुरोध) से डेटा प्राप्त करने के लिए जिम्मेदार है और इसका कार्यान्वयन उपयोगकर्ता इंटरफ़ेस से संबंधित नहीं है। यह याद रखना महत्वपूर्ण है कि नेटवर्क और स्थानीय प्रबंधक VIPER का हिस्सा नहीं हैं, लेकिन उन्हें अलग-अलग निर्भरता के रूप में माना जाता है।
- प्रस्तुतकर्ता : दृश्य में प्रदर्शित करने के लिए डेटा स्वरूपण के लिए जिम्मेदार। हमारे उदाहरण में MVVM में, इसके लिए ViewModelController जिम्मेदार था। प्रस्तुतकर्ता इंटरेक्टर से डेटा प्राप्त करता है, ViewModel (सही प्रदर्शन के लिए एक स्वरूपित वर्ग) का एक उदाहरण बनाता है और इसे व्यू में पास करता है। वह डेटा के उपयोगकर्ता इनपुट का जवाब भी देता है, डेटाबेस से अतिरिक्त डेटा का अनुरोध करता है, या इसके विपरीत, उसे पास करता है।
- इकाई : मॉडल परत की जिम्मेदारी का हिस्सा लेता है, जिसका उपयोग अन्य आर्किटेक्चर में किया जाता है। इकाई व्यापार तर्क के बिना, एक ऑनलाइन ट्रैक्टर और विभिन्न डेटा प्रबंधकों द्वारा प्रबंधित एक सरल डेटा ऑब्जेक्ट है।
- राउटर : सभी एप्लिकेशन नेविगेशन लॉजिक। ऐसा लग सकता है कि यह सबसे महत्वपूर्ण परत नहीं है, लेकिन अगर आपको उदाहरण के लिए, iPhone और iPad के लिए आवेदन पर एक ही दृश्य का पुन: उपयोग करने की आवश्यकता है, तो केवल एक चीज जो बदल सकती है वह यह है कि स्क्रीन पर आपके विचार कैसे दिखाई देते हैं। यह आपको राउटर को छोड़कर किसी भी अधिक परतों को नहीं छूने देता है, जो प्रत्येक मामले में इसके लिए जिम्मेदार होगा।
MVVM की तुलना में, VIPER में जिम्मेदारी के वितरण में कई महत्वपूर्ण अंतर हैं:
- उसके पास एक राउटर है, जो नेविगेशन के लिए एक अलग परत है
- एंटिटीज सरल डेटा ऑब्जेक्ट हैं, जो मॉडल से इंटरेक्टर तक डेटा एक्सेस करने की जिम्मेदारी का पुनर्वितरण करते हैं
- ViewModelController जिम्मेदारियों के बीच बातचीत और प्रस्तुतकर्ता के बीच साझा कर रहे हैं
और अब उसी एप्लिकेशन को दोहराते हैं, लेकिन पहले से ही VIPER पर। लेकिन आसानी से समझने के लिए, हम केवल संपर्कों के साथ एक नियंत्रक बनाएंगे। आप लिंक का उपयोग करके प्रोजेक्ट में एक नया संपर्क जोड़ने के लिए नियंत्रक के लिए कोड पा सकते हैं (
इस रिपॉजिटरी में VIPER संपर्क स्टार्टर फ़ोल्डर)।
नोट : यदि आप VIPER पर अपना प्रोजेक्ट बनाने का निर्णय लेते हैं, तो आपको मैन्युअल रूप से सभी फ़ाइलों को बनाने का प्रयास नहीं करना चाहिए - आप कोड जनरेटर में से किसी एक का उपयोग कर सकते हैं, उदाहरण के लिए, जैसे
VIPER जनरल या
जेनम्बा (रैम्बलर प्रोजेक्ट) ।
VIPER संपर्क ऐपदेखेंVIEW को Main.storyboard और ContactListView वर्ग के तत्वों द्वारा दर्शाया गया है। दृश्य बहुत निष्क्रिय है; प्रस्तोता से अधिसूचना पर, अपने एकमात्र कार्य प्रस्तुतकर्ता को इंटरफ़ेस इवेंट स्थानांतरित करना और अपने राज्य को अपडेट करना है। यह वही है जो
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 } }
व्यू
व्यूडेलडैड और
didClickOnAddButton घटनाओं को प्रस्तुतकर्ता को भेजता है। पहली घटना पर, प्रस्तुतकर्ता इंटरैक्टर से डेटा का अनुरोध करेगा, और दूसरे पर, प्रस्तुतकर्ता एक नया संपर्क जोड़ने के लिए राउटर को नियंत्रक पर स्विच करने के लिए कहेगा।
ContactListViewProtocol प्रोटोकॉल विधियों को प्रस्तुतकर्ता से या तो संपर्क सूची के लिए अनुरोध किया जाता है, या जब एक नया संपर्क जोड़ा जाता है। किसी भी स्थिति में, दृश्य में डेटा में केवल वही जानकारी होती है जो प्रदर्शन के लिए आवश्यक होती है।
व्यू में वे विधियाँ भी हैं जो UITableViewDataSource प्रोटोकॉल को लागू करती हैं जो प्राप्त आंकड़ों के साथ तालिका को पॉप्युलेट करती हैं।
INTERACTORहमारे उदाहरण में इंटरेक्टर काफी सरल है। वह जो कुछ भी करता है वह स्थानीय डेटाबेस प्रबंधक के माध्यम से डेटा का अनुरोध करता है, और यह उसके लिए कोई फर्क नहीं पड़ता कि यह प्रबंधक कोरडेटा, दायरे या किसी अन्य समाधान का उपयोग क्या करता है। ContactListInteractor में कोड निम्नानुसार होगा:
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([]) } } }
इंटरेक्टर द्वारा अनुरोधित डेटा प्राप्त करने के बाद, यह प्रस्तुतकर्ता को सूचित करता है। इसके अलावा, एक विकल्प के रूप में, सहभागिताकर्ता प्रस्तुतकर्ता को एक त्रुटि प्रेषित कर सकता है, जिसके बाद दृश्य में प्रदर्शन के लिए उपयुक्त दृश्य में त्रुटि को प्रारूपित करना होगा।
नोट : जैसा कि आपने देखा होगा, VIPER की प्रत्येक परत एक प्रोटोकॉल को लागू करती है। नतीजतन, कक्षाएं अमूर्तता पर निर्भर करती हैं, और किसी विशेष कार्यान्वयन पर नहीं, इस प्रकार निर्भरता उलटा के सिद्धांत को पूरा करना (एसओएलआईडी के सिद्धांतों में से एक)।
PRESENTERवास्तुकला का सबसे महत्वपूर्ण तत्व। व्यू और बाकी लेयर्स (इंटरेक्टर और राउटर) के बीच सभी संचार प्रस्तुतकर्ता के माध्यम से जाते हैं।
ContactListPresenter कोड:
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() {} }
व्यू लोड होने के बाद, यह प्रस्तुतकर्ता को सूचित करता है, जो बदले में इंटरेक्टर के माध्यम से डेटा का अनुरोध करता है। जब उपयोगकर्ता ऐड न्यू कॉन्टेक्ट बटन पर क्लिक करता है, तो व्यू प्रस्तुतकर्ता को सूचित करता है, जो राउटर में नई कॉन्टैक्ट स्क्रीन खोलने का अनुरोध भेजता है।
प्रस्तुतकर्ता डेटा को प्रारूपित भी करता है और संपर्क सूची से क्वेरी करने के बाद इसे दृश्य में लौटाता है। वह AddModuleDelegate प्रोटोकॉल को लागू करने के लिए भी जिम्मेदार है। इसका मतलब है कि प्रस्तुतकर्ता को एक नया संपर्क जोड़ने पर एक अधिसूचना प्राप्त होगी, प्रदर्शन के लिए संपर्क डेटा तैयार करें और दृश्य में स्थानांतरित करें।
जैसा कि आपने देखा होगा, प्रस्तुतकर्ता के पास काफी बोझिल होने का हर मौका है। यदि ऐसी कोई संभावना है, तो प्रस्तुतकर्ता को दो भागों में विभाजित किया जा सकता है: प्रस्तुतकर्ता, जो केवल डेटा प्राप्त करता है, इसे प्रदर्शन के लिए प्रारूपित करता है और इसे देखने के लिए पास करता है; और एक ईवेंट हैंडलर जो उपयोगकर्ता के कार्यों का जवाब देगा।
ENTITY कोयह परत MVVM में मॉडल लेयर के समान है। हमारे आवेदन में, यह संपर्क वर्ग और ऑपरेटर परिभाषा फ़ंक्शन <और> द्वारा दर्शाया गया है।
संपर्क सामग्री इस तरह दिखाई देगी:
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 में वे क्षेत्र होते हैं जो प्रस्तुतकर्ता भरता है (प्रारूप) जो दृश्य प्रदर्शित करता है। कॉन्टेक्ट क्लास NSManagedObject का एक उपवर्ग है जिसमें CoreData मॉडल के समान फ़ील्ड्स शामिल हैं।
रूटरऔर अंत में, अंतिम, लेकिन निश्चित रूप से महत्व नहीं, परत। नेविगेशन के लिए सभी जिम्मेदारी प्रस्तुतकर्ता और वायरफ्रेम के साथ टिकी हुई है। प्रस्तुतकर्ता उपयोगकर्ता से एक घटना प्राप्त करता है और जानता है कि संक्रमण कब करना है, और वायरफ्रेम को यह पता है कि इस संक्रमण को कैसे और कहां करना है। भ्रम को रोकने के लिए, इस उदाहरण में राउटर लेयर का संपर्क ContactListWireFrame वर्ग द्वारा किया जाता है और इसे पाठ में वायरफ्रेम के रूप में संदर्भित किया जाता है।
ContactListWireFrame कोड:
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) } } }
चूंकि वायरफ्रेम मॉड्यूल बनाने के लिए जिम्मेदार है, इसलिए यहां सभी निर्भरताओं को कॉन्फ़िगर करना सुविधाजनक होगा। जब आप एक अन्य नियंत्रक खोलना चाहते हैं, तो नया नियंत्रक खोलने वाला फ़ंक्शन एक तर्क के रूप में प्राप्त होता है जो इसे खोल देगा, और इसके वायरफ्रेम का उपयोग करके एक नया नियंत्रक बनाता है। साथ ही, एक नया नियंत्रक बनाते समय, आवश्यक डेटा को इसमें स्थानांतरित कर दिया जाता है, इस मामले में केवल प्रतिनिधि (संपर्कों के साथ नियंत्रक का प्रस्तुतकर्ता) बनाया गया संपर्क प्राप्त करने के लिए।
राउटर परत स्टोरीबोर्ड में सेग्यू (संक्रमण) के उपयोग से बचने और सभी कोड नेविगेशन को व्यवस्थित करने का एक अच्छा अवसर प्रदान करती है। चूंकि स्टोरीबोर्ड नियंत्रकों के बीच डेटा स्थानांतरित करने के लिए एक कॉम्पैक्ट समाधान प्रदान नहीं करता है, इसलिए हमारा नेविगेशन कार्यान्वयन अतिरिक्त कोड नहीं जोड़ेगा। हम सब मिल केवल सबसे अच्छा पुन: प्रयोज्य है।
सारांश :
आप
इस भंडार में दोनों परियोजनाओं को पा सकते हैं।
जैसा कि आप देख सकते हैं, MVVM और VIPER, हालांकि अलग-अलग हैं, अद्वितीय नहीं हैं। MVVM हमें बताता है कि व्यू और मॉडल के अलावा, एक ViewModel लेयर भी होनी चाहिए। लेकिन इस परत को कैसे बनाया जाना चाहिए, इसके बारे में कुछ भी नहीं कहा गया है, न ही डेटा के अनुरोध के बारे में - इस परत के लिए जिम्मेदारी स्पष्ट रूप से परिभाषित नहीं है। इसे लागू करने के कई तरीके हैं और आप इनमें से किसी का भी उपयोग कर सकते हैं।
दूसरी ओर, VIPER एक अद्वितीय वास्तुकला है। इसमें कई परतें शामिल हैं, जिनमें से प्रत्येक में जिम्मेदारी का एक अच्छी तरह से परिभाषित क्षेत्र है और एमवीवीएम से कम डेवलपर द्वारा प्रभावित है।
जब आर्किटेक्चर चुनने की बात आती है, तो आमतौर पर एकमात्र सही समाधान नहीं होता है, लेकिन फिर भी मैं कुछ सलाह देने की कोशिश करूंगा। यदि आपके पास एक बड़ी और लंबी परियोजना है, स्पष्ट आवश्यकताओं के साथ और आप घटकों के पुन: उपयोग के लिए पर्याप्त अवसर चाहते हैं, तो VIPER सबसे अच्छा समाधान होगा। जिम्मेदारी का एक स्पष्ट परिसीमन बेहतर परीक्षण को व्यवस्थित करने और पुन: उपयोग को बेहतर बनाने के लिए संभव बनाता है।