ReactiveCocoa 4,根据 UI 事件和验证正确发送我的 HTTP 请求

ReactiveCocoa 4, correctly send my HTTP request based on a UI event and a validation

我正在尝试了解 ReactiveCocoa 4 的一些概念,但没有找到一种方法来根据登录表单的输入正确验证和发送请求。
我当前的解决方案会在输入的每次有效更新时发送请求,这并不好。

看来我需要使用 ActionsCocoaActions 来解决我的问题,但我不明白如何正确实施它们。

这是我的代码示例:
我希望在按下登录按钮并且登录和密码字段都不为空时发送登录请求,否则我只会显示错误。
目前,当我修改输入字段时,生产者仍然活着并继续发送请求,这并不好...

我想要一个关于如何正确执行此操作的示例:)

LoginViewModel.swift

class LoginViewModel {
    let login = MutableProperty<String>("")
    let password = MutableProperty<String>("")

    init() {

    }

    func logIn() -> SignalProducer<Int, IntranetError> {
        return SignalProducer {
            observer, disposable in
            combineLatest(self.login.producer, self.password.producer)
            .promoteErrors(Moya.Error)
            .filter { (credentials : (String, String)) in

                guard credentials.0.length > 0 else {
                    observer.sendFailed(IntranetError.MissingLoginError)
                    return false
                }
                guard credentials.1.length > 0 else {
                    observer.sendFailed(IntranetError.MissingPasswordError)
                    return false
                }

                return true
            }
            .flatMap(.Latest) { (credentials : (String, String)) -> SignalProducer<User, Moya.Error> in
                let login = credentials.0
                let password = credentials.1
                return IntranetProvider.request(Intranet.LogIn(login, password)).filterSuccessfulStatusCodes()
                    .mapObject(User)
            }
            .start { (event) -> Void in
                switch event {
                case .Next(let user):
                    UserManager.sharedManager.user = user;
                    print(user)
                    observer.sendCompleted()
                case .Failed(let error):
                    observer.sendFailed(.MoyaError(error))
                default:
                    break
                }
            }
        }
    }
}

LoginViewController.swift

class LoginViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var loginTextField: FramedTextField!
    @IBOutlet weak var passwordTextField: FramedTextField!
    @IBOutlet weak var connectButton: UIButton!
    let viewModel : LoginViewModel = LoginViewModel()


    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.login <~ loginTextField.rac_text
        viewModel.password <~ passwordTextField.rac_text
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func connectButtonTouched(sender: AnyObject) {
        loginAsked()
    }

    func loginAsked() -> Void {
        SVProgressHUD.showWithMaskType(.Black)
        viewModel.logIn().throttle(0.5, onScheduler: QueueScheduler.mainQueueScheduler).start { (event) in
            switch event {
            case .Completed:
                SVProgressHUD.dismiss()
                self.connectionSuccessfull()
            case .Failed(let error):
                switch error {
                case .MissingPasswordError :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Missing password", comment: "User password is missing"))
                case .MissingLoginError :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Missing login", comment: "User login is missing"))
                case .MoyaError(let error) :
                    SVProgressHUD.showErrorWithStatus(error.toString())
                default :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Internal error, please try again later", comment: ""))
                }
            default:
                break
            }
        }
    }
}

谢谢

经过漫长的一夜努力,我终于找到了正确实施它的方法。
最后验证过程我想多了,现在简单多了。
结果:

LoginViewModel.swift

class LoginViewModel {

    let login = MutableProperty<String>("")
    let password = MutableProperty<String>("")

    var loginAction : Action<(String, String), User, IntranetError>!
    var cocoaActionLogin : CocoaAction!

    init() {
        loginAction = Action { (let login, let password) in
            return SignalProducer {
                observer, disposable in
                guard login.length > 0 else {
                    observer.sendFailed(IntranetError.MissingLoginError)
                    return
                }
                guard password.length > 0 else {
                    observer.sendFailed(IntranetError.MissingPasswordError)
                    return
                }
                print("SENT")
                IntranetProvider.request(Intranet.LogIn(login, password))
                .filterSuccessfulStatusCodes()
                .mapObject(User)
                .start { (event) in
                    switch event {
                    case .Next(let user):
                        UserManager.sharedManager.user = user;
                        print(user)
                        observer.sendCompleted()
                    case .Failed(let error):
                        observer.sendFailed(.MoyaError(error))
                    default:
                        observer.sendCompleted()
                    }
                }
            }
        }
        cocoaActionLogin = CocoaAction(loginAction) { _ in
            return (self.login.value, self.password.value)
        }
    }
}

LoginViewController.swift

class LoginViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var loginTextField: FramedTextField!
    @IBOutlet weak var passwordTextField: FramedTextField!
    @IBOutlet weak var connectButton: UIButton!
    let viewModel : LoginViewModel = LoginViewModel()


    override func viewDidLoad() {
        super.viewDidLoad()
        viewModel.login <~ loginTextField.rac_text
        viewModel.password <~ passwordTextField.rac_text
        connectButton.addTarget(self.viewModel.cocoaActionLogin, action: CocoaAction.selector, forControlEvents: .TouchUpInside)
        self.viewModel.loginAction.events
            .observeOn(UIScheduler())
            .observeNext { (event) in
            switch event {
            case .Completed:
                SVProgressHUD.dismiss()
                self.connectionSuccessfull()
            case .Failed(let error):
                switch error {
                case .MissingPasswordError :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Missing password", comment: "User password is missing"))
                case .MissingLoginError :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Missing login", comment: "User login is missing"))
                case .MoyaError(let error) :
                    SVProgressHUD.showErrorWithStatus(error.toString())
                default :
                    SVProgressHUD.showErrorWithStatus(NSLocalizedString("Internal error, please try again later", comment: ""))
                }
            default:
                break
            }

        }
    }
}