2014-11-28 7 views
24

मैं एक आईओएस डेवलपर हूं और मैं अपनी परियोजनाओं में भारी दृश्य नियंत्रक होने का दोषी हूं इसलिए मैं अपनी परियोजनाओं को ढांचा बनाने के लिए एक बेहतर तरीका खोज रहा हूं और एमवीवीएम (मॉडल-व्यू) -ViewModel) वास्तुकला। मैं आईओएस के साथ बहुत सारे एमवीवीएम पढ़ रहा हूं और मेरे पास कुछ प्रश्न हैं। मैं एक उदाहरण के साथ अपने मुद्दों की व्याख्या करेंगे।आईओएस में एमवीवीएम का उपयोग

मेरे पास एक दृश्य नियंत्रक है जिसे LoginViewController कहा जाता है।

LoginViewController.swift

import UIKit 

class LoginViewController: UIViewController { 

    @IBOutlet private var usernameTextField: UITextField! 
    @IBOutlet private var passwordTextField: UITextField! 

    private let loginViewModel = LoginViewModel() 

    override func viewDidLoad() { 
     super.viewDidLoad() 

    } 

    @IBAction func loginButtonPressed(sender: UIButton) { 
     loginViewModel.login() 
    } 
} 

यह एक मॉडल वर्ग नहीं है। लेकिन मैंने सत्यापन तर्क और नेटवर्क कॉल रखने के लिए LoginViewModel नामक एक दृश्य मॉडल बनाया था।

LoginViewModel.swift

import Foundation 

class LoginViewModel { 

    var username: String? 
    var password: String? 

    init(username: String? = nil, password: String? = nil) { 
     self.username = username 
     self.password = password 
    } 

    func validate() { 
     if username == nil || password == nil { 
      // Show the user an alert with the error 
     } 
    } 

    func login() { 
     // Call the login() method in ApiHandler 
     let api = ApiHandler() 
     api.login(username!, password: password!, success: { (data) -> Void in 
      // Go to the next view controller 
     }) { (error) -> Void in 
      // Show the user an alert with the error 
     } 
    } 
} 
  1. मेरा पहला सवाल बस है मेरी MVVM कार्यान्वयन सही है? मुझे यह संदेह है क्योंकि उदाहरण के लिए मैंने नियंत्रक में लॉगिन बटन की टैप इवेंट (loginButtonPressed) डाला है। मैंने लॉगिन स्क्रीन के लिए एक अलग दृश्य नहीं बनाया क्योंकि इसमें केवल कुछ टेक्स्टफील्ड और एक बटन है। क्या नियंत्रक के लिए यूआई तत्वों से जुड़ी घटना विधियों के लिए स्वीकार्य है?

  2. मेरा अगला प्रश्न लॉगिन बटन के बारे में भी है। जब उपयोगकर्ता बटन को टैप करता है, तो उपयोगकर्ता नाम और पासवर्ड मानों को सत्यापन के लिए LoginViewModel में गुजरना चाहिए और यदि सफल हो, तो API कॉल पर। मेरा सवाल दृश्य मॉडल को मानों को कैसे पास करना है। क्या मुझे login() विधि में दो पैरामीटर जोड़ना चाहिए और जब मैं इसे व्यू कंट्रोलर से कॉल करता हूं तो उन्हें पास कर सकता हूं? या क्या मुझे उनके लिए दृश्य मॉडल में गुण घोषित करना चाहिए और दृश्य मानकों से अपने मूल्य निर्धारित करना चाहिए? एमवीवीएम में कौन सा स्वीकार्य है?

  3. व्यू मॉडल में validate() विधि लें। यदि उनमें से कोई खाली है तो उपयोगकर्ता को अधिसूचित किया जाना चाहिए। इसका मतलब है जांच के बाद, आवश्यक कार्रवाई करने के लिए परिणाम को देखने के लिए परिणाम नियंत्रक को वापस किया जाना चाहिए (एक चेतावनी दिखाएं)। login() विधि के साथ वही बात। अगर अनुरोध विफल हो जाता है या अगले दृश्य नियंत्रक पर जाता है तो उपयोगकर्ता को चेतावनी दें। मैं दृश्य मॉडल से इन घटनाओं के नियंत्रक को कैसे सूचित करूं? क्या इस तरह के मामलों में केवीओ जैसे बाध्यकारी तंत्र का उपयोग करना संभव है?

  4. आईओएस के लिए एमवीवीएम का उपयोग करते समय अन्य बाध्यकारी तंत्र क्या हैं? केवीओ एक है। लेकिन मैंने पढ़ा है कि यह बड़ी परियोजनाओं के लिए काफी उपयुक्त नहीं है क्योंकि इसे बहुत सारे बॉयलरप्लेट कोड की आवश्यकता होती है (पर्यवेक्षकों को पंजीकृत/अनधिकृत करना आदि)। अन्य विकल्प क्या हैं? मुझे पता है कि रेक्टिव कोकोआ इसके लिए एक ढांचा है, लेकिन मैं यह देखने के लिए देख रहा हूं कि कोई अन्य मूल है या नहीं।

सभी सामग्री मैं पर इंटरनेट इन भागों मैं स्पष्ट करने के लिए देख रहा हूँ पर कोई जानकारी नहीं करने के लिए थोड़ा प्रदान की MVVM पर भर में आया था, इसलिए मैं वास्तव में अपनी प्रतिक्रियाओं की सराहना करेंगे।

+0

क्या यह केवल मुझे या किसी और को दृश्य मॉडल से बनाए गए नेटवर्क अनुरोध पसंद नहीं हैं? – SoftDesigner

+0

@ सॉफ़्टडिज़ाइनर मैं मानता हूं, दृश्य मॉडल में नेटवर्क कॉल न करने का सबसे अच्छा अभ्यास है, लेकिन उदाहरण में, एपीएचंडलर क्लास ने लॉगिन को कैसे किया जा रहा है इस बारे में विशिष्टताओं को सफलतापूर्वक समझाया है। इस बिंदु पर यह एक सबसे अच्छा अनुमान है कि वास्तव में एक नेटवर्क कॉल हो रहा है। ऐप ऑफ़लाइन हो सकता है और स्थानीय डीबी के माध्यम से लॉग इन कर सकता है। हम नहीं जानते और न ही दृश्य मॉडल (जो यह होना चाहिए)। यह बेहतर होगा अगर एपीआई चर के लिए डेटा प्रकार एक प्रोटोकॉल था जिसे एपीएचंडलर द्वारा कार्यान्वित किया गया था। –

उत्तर

28

waddup दोस्त!

1 ए- आप सही दिशा में आगे बढ़ रहे हैं। आपने लॉग इन बटन को व्यू कंट्रोलर में दबाया है और यह वही है जहां यह होना चाहिए। नियंत्रण के लिए इवेंट हैंडलर हमेशा व्यू कंट्रोलर में जाना चाहिए - ताकि यह सही हो।

1b - आपके व्यू मॉडल में आपके पास टिप्पणियां हैं, "उपयोगकर्ता को त्रुटि के साथ एक चेतावनी दिखाएं"।आप वैध त्रुटि के भीतर से उस त्रुटि को प्रदर्शित नहीं करना चाहते हैं। इसके बजाय एक enum बनाएँ जिसमें एक संबद्ध मान है (जहां मान वह त्रुटि संदेश है जिसे आप उपयोगकर्ता को प्रदर्शित करना चाहते हैं)। अपनी वैध विधि बदलें ताकि वह उस enum को वापस कर सके। फिर अपने व्यू कंट्रोलर के भीतर आप उस रिटर्न वैल्यू का मूल्यांकन कर सकते हैं और वहां से आप अलर्ट संवाद प्रदर्शित करेंगे। याद रखें कि आप केवल दृश्य नियंत्रक के भीतर UIKit संबंधित कक्षाओं का उपयोग करना चाहते हैं - कभी भी दृश्य मॉडल से नहीं। मॉडल देखें केवल व्यापार तर्क होना चाहिए।

enum StatusCodes : Equatable 
{ 
    case PassedValidation 
    case FailedValidation(String) 

    func getFailedMessage() -> String 
    { 
     switch self 
     { 
     case StatusCodes.FailedValidation(let msg): 
      return msg 

     case StatusCodes.OperationFailed(let msg): 
      return msg 

     default: 
      return "" 
     } 
    } 
} 

func ==(lhs : StatusCodes, rhs : StatusCodes) -> Bool 
{ 
    switch (lhs, rhs) 
    {   
    case (.PassedValidation, .PassedValidation): 
     return true 

    case (.FailedValidation, .FailedValidation): 
     return true 

    default: 
     return false 
    } 
} 

func !=(lhs : StatusCodes, rhs : StatusCodes) -> Bool 
{ 
    return !(lhs == rhs) 
} 

func validate(username : String, password : String) -> StatusCodes 
{ 
    if username.isEmpty || password.isEmpty 
    { 
      return StatusCodes.FailedValidation("Username and password are required") 
    } 

    return StatusCodes.PassedValidation 
} 

2 - यह वरीयता का मामला है और अंततः आपके ऐप की आवश्यकताओं के अनुसार निर्धारित किया जाता है। मेरे ऐप में मैं इन मानों को लॉगिन() विधि i.e. लॉगिन (उपयोगकर्ता नाम, पासवर्ड) के माध्यम से पास करता हूं।

3 - LoginEventsDelegate नाम के एक प्रोटोकॉल बनाएं और फिर इस तरह के रूप में यह के भीतर एक विधि है:

func loginViewModel_LoginCallFinished(successful : Bool, errMsg : String) 

हालांकि इस पद्धति पर ही प्रवेश का प्रयास करने के वास्तविक परिणामों को देखते हुए नियंत्रक को सूचित करने के लिए इस्तेमाल किया जाना चाहिए रीमोट सर्वर। यह सत्यापन भाग के साथ कुछ भी नहीं होना चाहिए। # 1 में ऊपर चर्चा के अनुसार आपका सत्यापन दिनचर्या संभाला जाएगा। अपने व्यू कंट्रोलर को LoginEventsDelegate लागू करें। और आपके एपीआई के लिए पूरा होने के ब्लॉक में फिर यानी

class LoginViewModel { 
    var delegate : LoginEventsDelegate? 
} 

आपके विचार मॉडल पर एक सार्वजनिक संपत्ति बनाने के फोन आप यानी

func login() { 
     // Call the login() method in ApiHandler 
     let api = ApiHandler() 

     let successBlock = 
     { 
      [weak self](data) -> Void in 

      if let this = self { 
       this.delegate?.loginViewModel_LoginCallFinished(true, "") 
      } 
     } 

     let errorBlock = 
     { 
      [weak self] (error) -> Void in 

      if let this = self { 
       var errMsg = (error != nil) ? error.description : "" 
       this.delegate?.loginViewModel_LoginCallFinished(error == nil, errMsg) 
      } 
     } 

     api.login(username!, password: password!, success: successBlock, error: errorBlock) 
    } 

प्रतिनिधि के माध्यम से दृश्य नियंत्रक को सूचित कर सकते हैं और आपका दृश्य नियंत्रक कैसा लगेगा यह:

class loginViewController : LoginEventsDelegate { 

    func viewDidLoad() { 
     viewModel.delegate = self 
    } 

    func loginViewModel_LoginCallFinished(successful : Bool, errMsg : String) { 
     if successful { 
      //segue to another view controller here 
     } else { 
      MsgBox(errMsg) 
     } 
    } 
} 

कुछ लोग कहेंगे कि आप केवल लॉगिन विधि को बंद कर सकते हैं और प्रोटोकॉल को पूरी तरह से छोड़ सकते हैं। ऐसा कुछ कारण हैं जो मुझे लगता है कि यह एक बुरा विचार है।

बिजनेस लॉजिक लेयर (बीएलएल) को यूआई लेयर (यूआईएल) से बंद करने से पास करने से चिंता का पृथक्करण (एसओसी) टूट जाएगा। लॉग इन() विधि बीएलएल में रहती है इसलिए अनिवार्य रूप से आप कहेंगे "अरे बीएलएल मेरे लिए इस यूआईएल तर्क को निष्पादित करता है"। यह एक एसओसी नंबर नहीं है!

बीएलएल को केवल प्रतिनिधि सूचनाओं के माध्यम से यूआईएल के साथ संवाद करना चाहिए। इस तरह बीएलएल अनिवार्य रूप से कह रहा है, "हे उइल, मैं अपने तर्क को निष्पादित कर चुका हूं और यहां कुछ डेटा तर्क हैं जिनका उपयोग आप यूआई नियंत्रणों में हेरफेर करने के लिए कर सकते हैं जैसा आपको चाहिए"।

तो यूआईएल को कभी भी बीएलएल से उसके लिए यूआई नियंत्रण तर्क निष्पादित करने के लिए नहीं पूछना चाहिए। केवल बीएलएल से उसे सूचित करने के लिए कहें।

4 - मैंने रेएक्टिव कोको देखा है और इसके बारे में अच्छी चीजें सुनी हैं लेकिन इसका कभी भी उपयोग नहीं किया है। तो व्यक्तिगत अनुभव से बात नहीं कर सकते हैं। मैं देखता हूं कि सरल प्रतिनिधि सूचना का उपयोग कैसे करें (जैसा कि # 3 में वर्णित है) आपके परिदृश्य में आपके लिए काम करता है। यदि यह आवश्यकता को पूरा करता है तो महान, अगर आप कुछ और जटिल परिसर की तलाश में हैं तो शायद ReactiveCocoa में देखें।

बीटीडब्ल्यू, यह तकनीकी रूप से एमवीवीएम दृष्टिकोण नहीं है क्योंकि बाध्यकारी और कमांड का उपयोग नहीं किया जा रहा है, लेकिन यह सिर्फ "टा-मे-टो" है। आईएमएचओ नाइटपिकिंग "टा-मा-टो"। आपके द्वारा उपयोग किए जाने वाले एमवी * दृष्टिकोण के बावजूद एसओसी सिद्धांत सभी समान हैं।

+1

वास्तव में यहां बहुत अच्छा जवाब है! क्या आप अपने उत्तर 3 के बारे में कुछ और बता सकते हैं? जो हिस्सा आप कहते हैं: लॉगिन विधि को बंद करने और प्रोटोकॉल को छोड़ने के बजाय प्रतिनिधि दृष्टिकोण के साथ चिपकते हैं? या शायद इस मुद्दे के कुछ संबंधित लिंक दे? वास्तव में सराहना!! –

+0

बिजनेस लॉजिक लेयर (बीएलएल) को यूआई लेयर (यूआईएल) से बंद करने से पास करने से चिंता का पृथक्करण (एसओसी) टूट जाएगा। लॉग इन() विधि बीएलएल में रहती है इसलिए अनिवार्य रूप से आप कहेंगे "अरे बीएलएल मेरे लिए इस यूआईएल तर्क को निष्पादित करता है"। यह एक एसओसी नंबर नहीं है! बीएलएल को केवल प्रतिनिधि सूचनाओं के माध्यम से यूआईएल के साथ संवाद करना चाहिए।इस तरह बीएलएल अनिवार्य रूप से कह रहा है, "हे उइल, मैं अपने तर्क को निष्पादित कर चुका हूं और यहां कुछ डेटा तर्क हैं जिनका उपयोग आप यूआई नियंत्रणों में हेरफेर करने के लिए कर सकते हैं जैसा आपको चाहिए"। तो यूआईएल को कभी भी बीएलएल से उसके लिए यूआई नियंत्रण तर्क निष्पादित करने के लिए नहीं कहना चाहिए। केवल बीएलएल से उसे सूचित करने के लिए कहें। –

+0

लेकिन क्या होगा यदि आपके बंद में self.refresh() जैसे एकल कथन शामिल हैं? यह एक प्रतिनिधि विधि से अलग कैसे है जो एक ही self.refresh() विधि को अंदर भी बुलाता है? अंत में, नियंत्रण किसी भी तरह से आपके व्यू कंट्रोलर के एक अद्यतन विधि में एक चयनित संचार तरीके से स्वतंत्र है। – dieworld

8

आईओएस में एमवीवीएम का मतलब है कि आपकी स्क्रीन का उपयोग आपके मॉडल वर्गों से अलग-अलग डेटा से भरा हुआ है। यह आम तौर पर आपके यूआई में मौजूद सभी आइटमों को मानचित्र करता है जो लेबल, टेक्स्टबॉक्स, डेटा स्रोत या गतिशील छवियों जैसे डेटा का उपभोग या उत्पादन करते हैं। यह अक्सर इनपुट के कुछ प्रकाश सत्यापन (खाली फ़ील्ड, मान्य ईमेल है या नहीं, सकारात्मक संख्या, स्विच चालू है या नहीं) वैधकर्ताओं के साथ करता है।ये वैधकर्ता आमतौर पर अलग-अलग कक्षाएं हैं जो इनलाइन तर्क नहीं हैं।

आपकी व्यू परत इस वीएम कक्षा के बारे में जानता है और इसमें उन्हें प्रतिबिंबित करने के लिए परिवर्तनों को देखता है और जब उपयोगकर्ता डेटा इनपुट करता है तो वीएम कक्षा भी अपडेट करता है। वीएम में सभी गुण यूआई में वस्तुओं से बंधे हैं। तो उदाहरण के लिए कोई उपयोगकर्ता उपयोगकर्ता पंजीकरण स्क्रीन पर जाता है, इस स्क्रीन को एक वीएम प्राप्त होता है जिसमें किसी भी संपत्ति की संपत्ति को छोड़कर इसकी कोई भी संपत्ति नहीं होती है जिसमें अपूर्ण स्थिति होती है। व्यू जानता है कि केवल एक पूर्ण फॉर्म सबमिट किया जा सकता है, इसलिए यह अब सबमिट बटन को निष्क्रिय करता है।

तब उपयोगकर्ता इसके विवरण भरना शुरू कर देता है और ई-मेल पता प्रारूप में गलती करता है। वीएम में उस फ़ील्ड के लिए वैलिडेटर अब एक त्रुटि स्थिति सेट करता है और दृश्य त्रुटि स्थिति (उदाहरण के लिए लाल सीमा) और यूआई में वीएम सत्यापनकर्ता में त्रुटि संदेश सेट करता है।

अंत में, जब वीएम के अंदर सभी आवश्यक फ़ील्ड स्थिति प्राप्त करते हैं तो पूर्ण VM पूरा हो जाता है, दृश्य देखता है और अब सबमिट बटन को सक्रिय करने के लिए सेट करता है ताकि उपयोगकर्ता इसे सबमिट कर सके। सबमिट बटन कार्रवाई वीसी को तारित की जाती है और वीसी सुनिश्चित करता है कि वीएम सही मॉडल (ओं) से जुड़ा हुआ हो और बचाया जाए। कभी-कभी मॉडल को वीएम के रूप में सीधे उपयोग किया जाता है, यह तब उपयोगी हो सकता है जब आपके पास सरल सीआरयूडी जैसी स्क्रीन हों।

मैंने WPF में इस पैटर्न के साथ काम किया है और यह वास्तव में बहुत अच्छा काम करता है। यह उन सभी पर्यवेक्षकों को दृश्यों में स्थापित करने और मॉडल कक्षाओं के साथ-साथ व्यूमोडेल कक्षाओं में बहुत से फ़ील्ड लगाने में बहुत परेशानी की तरह लगता है लेकिन एक अच्छा एमवीवीएम ढांचा आपको इससे मदद करेगा। आपको यूआई तत्वों को सही प्रकार के वीएम तत्वों से जोड़ने की ज़रूरत है, सही सत्यापनकर्ता असाइन करें और इस बॉयलरप्लेट कोड को स्वयं जोड़ने के बिना आपके लिए बहुत सारी नलसाजी आपके लिए की जाती है।

इस पैटर्न के कुछ लाभ:

  • यह केवल उजागर करता है डेटा आप आंकड़ों के UI तत्व कनेक्ट करने के लिए
  • बेहतर testability
  • कम बॉयलरप्लेट कोड की जरूरत

नुकसान:

  • अब तुम दोनों एम और वीएम
  • बनाए रखने के लिए आप अभी भी पूरी तरह से कुलपति iOS का उपयोग कर के आसपास नहीं मिल सकता की जरूरत है।
संबंधित मुद्दे