2016-03-02 11 views
7

मैं RxSwift के साथ एक एमवीवीएम लिखने की कोशिश कर रहा हूं और उद्देश्य-सी के लिए प्रतिक्रियाशील कोको में जो कुछ भी करने के लिए उपयोग किया गया था उसकी तुलना करना, मेरी सेवा को सही तरीके से लिखना थोड़ा मुश्किल रहा है।RxSwift सही तरीका

एक उदाहरण एक लॉगिन सेवा है।

ReactiveCocoa (ऑब्जेक्टिव-सी) इस तरह मैं कोड कुछ के साथ

:

// ViewController 


// send textfield inputs to viewmodel 
RAC(self.viewModel, userNameValue) = self.fieldUser.rac_textSignal; 
RAC(self.viewModel, userPassValue) = self.fieldPass.rac_textSignal; 

// set button action 
self.loginButton.rac_command = self.viewModel.loginCommand; 

// subscribe to login signal 
[[self.viewModel.loginResult deliverOnMainThread] subscribeNext:^(NSDictionary *result) { 
    // implement 
} error:^(NSError *error) { 
    NSLog(@"error"); 
}]; 

और मेरे ViewModel इस तरह होना चाहिए:

// valid user name signal 
self.isValidUserName = [[RACObserve(self, userNameValue) 
         map:^id(NSString *text) { 
          return @(text.length > 4); 
         }] distinctUntilChanged]; 

// valid password signal 
self.isValidPassword = [[RACObserve(self, userPassValue) 
         map:^id(NSString *text) { 
          return @(text.length > 3); 
         }] distinctUntilChanged]; 

// merge signal from user and pass 
self.isValidForm = [RACSignal combineLatest:@[self.isValidUserName, self.isValidPassword] 
              reduce:^id(NSNumber *user, NSNumber *pass){ 
               return @([user boolValue] && [pass boolValue]); 
              }]; 


// login button command 
self.loginCommand = [[RACCommand alloc] initWithEnabled:self.isValidForm 
              signalBlock:^RACSignal *(id input) { 
               return [self executeLoginSignal]; 
              }]; 

अब RxSwift में मैं के रूप में ही लिखा है:

// ViewController 

// initialize viewmodel with username and password bindings 
    viewModel = LoginViewModel(withUserName: usernameTextfield.rx_text.asDriver(), password: passwordTextfield.rx_text.asDriver()) 

// subscribe to isCredentialsValid 'Signal' to assign button state 
    viewModel.isCredentialsValid 
     .driveNext { [weak self] valid in 
      if let button = self?.signInButton { 
       button.enabled = valid 
      } 
    }.addDisposableTo(disposeBag) 

// signinbutton 
    signInButton.rx_tap 
     .withLatestFrom(viewModel.isCredentialsValid) 
     .filter { $0 } 
     .flatMapLatest { [unowned self] valid -> Observable<AutenticationStatus> in 
      self.viewModel.login(self.usernameTextfield.text!, password: self.passwordTextfield.text!) 
      .observeOn(SerialDispatchQueueScheduler(globalConcurrentQueueQOS: .Default)) 
     } 
     .observeOn(MainScheduler.instance) 
     .subscribeNext { 
      print($0) 
     }.addDisposableTo(disposeBag) 

मैं बटन राज्य इस तरह से बदल रहा हूँ क्योंकि मैं यह काम करने के लिए नहीं कर सकते हैं:

viewModel.isCredentialsValid.drive(self.signInButton.rx_enabled).addDisposableTo(disposeBag) 

और मेरे ViewModel

let isValidUser = username 
    .distinctUntilChanged() 
     .map { $0.characters.count > 3 } 

    let isValidPass = password 
    .distinctUntilChanged() 
     .map { $0.characters.count > 2 } 

    isCredentialsValid = Driver.combineLatest(isValidUser, isValidPass) { $0 && $1 } 

और

func login(username: String, password: String) -> Observable<AutenticationStatus> 
{ 
    return APIServer.sharedInstance.login(username, password: password) 
} 

मैं ड्राइवर का उपयोग कर रहा है क्योंकि यह जैसे कुछ अच्छा सुविधाओं लपेट: catchErrorJustReturn(), लेकिन मैं वास्तव में पसंद नहीं है जिस तरह से मैं यह कर रहा हूं:

1) मुझे देखने के लिए पैरामीटर के रूप में उपयोगकर्ता नाम और पासवर्ड फ़ील्ड भेजना है मॉडल (वैसे, हल करने में आसान है)

2) मुझे पसंद नहीं है कि मेरा व्यू कंट्रोलर लॉग इन बटन टैप होने पर सभी काम करता है, देखें नियंत्रक को यह जानने की आवश्यकता नहीं है कि लॉगिन सेवा प्राप्त करने के लिए उसे कौन सी सेवा कॉल करनी चाहिए, यह है एक दृश्य मॉडल नौकरी।

3) मैं सदस्यता के बाहर उपयोगकर्ता नाम और पासवर्ड के संग्रहीत मूल्य तक नहीं पहुंच सकता।

क्या ऐसा करने का कोई अलग तरीका है? आप इस तरह की चीज कैसे कर रहे हैं? बहुत बहुत धन्यवाद।

उत्तर

8

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

enum ServerResponse { 
    case Failure(cause: String) 
    case Success 
} 

protocol APIServerService { 
    func authenticatedLogin(username username: String, password: String) -> Observable<ServerResponse> 
} 

protocol ValidationService { 
    func validUsername(username: String) -> Bool 
    func validPassword(password: String) -> Bool 
} 


struct LoginViewModel { 

    private let disposeBag = DisposeBag() 

    let isCredentialsValid: Driver<Bool> 
    let loginResponse: Driver<ServerResponse> 


    init(
    dependencies:(
     APIprovider: APIServerService, 
     validator: ValidationService), 
    input:(
     username:Driver<String>, 
     password: Driver<String>, 
     loginRequest: Driver<Void>)) { 


    isCredentialsValid = Driver.combineLatest(input.username, input.password) { dependencies.validator.validUsername($0) && dependencies.validator.validPassword($1) } 

    let usernameAndPassword = Driver.combineLatest(input.username, input.password) { ($0, $1) } 

    loginResponse = input.loginRequest.withLatestFrom(usernameAndPassword).flatMapLatest { (username, password) in 

     return dependencies.APIprovider.authenticatedLogin(username: username, password: password) 
     .asDriver(onErrorJustReturn: ServerResponse.Failure(cause: "Network Error")) 
    } 
    } 
} 

और अब अपने ViewController और निर्भरता कुछ इस तरह दिखाई:

struct Validation: ValidationService { 
    func validUsername(username: String) -> Bool { 
    return username.characters.count > 4 
    } 

    func validPassword(password: String) -> Bool { 
    return password.characters.count > 3 
    } 
} 


struct APIServer: APIServerService { 
    func authenticatedLogin(username username: String, password: String) -> Observable<ServerResponse> { 
    return Observable.just(ServerResponse.Success) 
    } 
} 

class LoginMVVMViewController: UIViewController { 

    @IBOutlet weak var usernameTextField: UITextField! 
    @IBOutlet weak var passwordTextField: UITextField! 
    @IBOutlet weak var loginButton: UIButton! 

    let loginRequestPublishSubject = PublishSubject<Void>() 

    lazy var viewModel: LoginViewModel = { 
    LoginViewModel(
     dependencies: (
     APIprovider: APIServer(), 
     validator: Validation() 
    ), 
     input: (
     username: self.usernameTextField.rx_text.asDriver(), 
     password: self.passwordTextField.rx_text.asDriver(), 
     loginRequest: self.loginButton.rx_tap.asDriver() 
    ) 
    ) 
    }() 

    let disposeBag = DisposeBag() 

    override func viewDidLoad() { 
    super.viewDidLoad() 

    viewModel.isCredentialsValid.drive(loginButton.rx_enabled).addDisposableTo(disposeBag) 

    viewModel.loginResponse.driveNext { loginResponse in 

     print(loginResponse) 

    }.addDisposableTo(disposeBag) 
    } 
} 

अपने विशिष्ट प्रश्नों के लिए:

1.I यूज़रनेम और पासवर्ड क्षेत्रों भेजने के लिए दृश्य मॉडल के लिए पैरामीटर के रूप में (वैसे, यह हल करना आसान है)

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

दूसरे शब्दों में, अपने व्यू-मॉडल में import UIKit नहीं करें और आप ठीक होंगे।

2. मुझे अपना दृश्य तरीका पसंद नहीं है नियंत्रक लॉग इन बटन टैप होने पर सभी काम करते हैं, देखें नियंत्रक को यह जानने की आवश्यकता नहीं है कि लॉगिन सेवा प्राप्त करने के लिए उसे कौन सी सेवा कॉल करनी चाहिए, यह एक दृश्य मॉडल है।

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

  1. मैं सदस्यता के बाहर उपयोगकर्ता नाम और पासवर्ड के संग्रहीत मूल्य तक नहीं पहुंच सकता।

आपको उन मानों को आरएक्स तरीके से एक्सेस करना चाहिए। जैसे व्यू-मॉडल को वेरिएबल प्रदान करने दें जो आपको कुछ मानदंडों के बाद, या एक चालक जो आपको प्रासंगिक घटनाएं देता है (उदाहरण के लिए एक अलर्ट व्यू दिखाएं जो "उपयोगकर्ता (उपयोगकर्ता नाम) आपके उपयोगकर्ता नाम को पूछता है?" लॉगिन अनुरोध भेजने से पहले)। इस तरह से आप राज्य से बचने, और तुल्यकालन समस्याओं (जैसे मैं संग्रहीत मूल्य गया और एक लेबल पर प्रस्तुत लेकिन एक दूसरे बाद में इसे अपडेट किए गए हैं और एक अन्य लेबल को अपडेट मूल्य प्रस्तुत कर रहा है) Microsoft

से

MVVM आरेख enter image description here

आशा है कि आप यह जानकारी मददगार :)

संबंधित लेख मिलेगा: आईओएस करके ऐश कुंडके लिए

मॉडल-व्यू-ViewModel RxSwift दुनिया में

ViewModel Serg Dort https://medium.com/@SergDort/viewmodel-in-rxswift-world-13d39faa2cf5#.wuthixtp9

तक
संबंधित मुद्दे