ReactiveCocoa 学习笔记

2017-08-25  本文已影响0人  心甘情愿_Root

ReactiveCocoa

配置安装RAC

项目目录下执行

$ touch Cartfile
$ vim Cartfile

并直接写入Cartfile

github "ReactiveCocoa/ReactiveCocoa" ~> 6.0

保存后直接执行,注意我的版本,可能与实际的版本有差别。

$ carthage update
*** Cloning ReactiveCocoa
*** Cloning ReactiveSwift
*** Cloning Result
*** Checking out Result at "3.2.3"
*** Checking out ReactiveCocoa at "6.0.1"
*** Checking out ReactiveSwift at "2.0.1"

carthage 会自动下载对应的frameworks,主要有:
ReactiveCocoaReactiveSwiftResult三个对应框架

下载完成后参考我的另外一篇carthage集成添加到项目中。

项目目录下执行

$ touch Podfile
$ vim Podfile

并直接写入Podfile

platform:ios, '8.0'
use_frameworks!
target "yourtarget" do
...
pod 'ReactiveCocoa','~>6.0'
end

保存后直接执行,注意我的版本,可能与实际的版本有差别。

$ pod install
...

swift 中使用 RAC + MVVM

第一步:引入头文件

import Result
import ReactiveSwift
import ReactiveCocoa

第二步:创建服务及信号管道

class LoginService {
    let (requestSignal, requestObserver) = Signal<String, NoError>.pipe()
    
    func canUseAccount(_ string : String) -> SignalProducer<Bool, NoError> {
        return SignalProducer { observer, disposable in
            self.requestObserver.send(value: string)
            observer.send(value: true)
            observer.sendCompleted()
        }
    }
}

第三步:创建ViewModel

创建LoginModel类,并添加构造方法传入服务

class LoginModel {  
    // 添加Error子类,FormError
    struct FormError : Error {
        let reason : String
        
        static let invalidAccount = FormError(reason: "请输入正确的账号")
        static let mismatchAccount = FormError(reason: "账号输入不匹配")
        static let accountUnavaliable = FormError(reason: "账号已存在")
    }
    
    init(loginService : LoginService) {
    
    }
}

第四步:在ViewModel中添加属性、行为和信号量

class LoginModel {  
...
    let account : ValidatingProperty<String, FormError>
    let accountConfirm : ValidatingProperty<String, FormError>
    let termsAccepted : MutableProperty<Bool>
    
    let submit : Action <(), (), FormError>
    
    let reasons : Signal<String, NoError>
...
}

第五步:初始化ViewModel的属性、行为和信号量

...
init(loginService : LoginService) {
    // 账号属性,""指定input为String,判断账号是否有效,返回.valid则符合条件,.invalid(FormError)则输出不符合日志。
    account = ValidatingProperty("") { input in
        return input.hasSuffix("aha") ? .valid : .invalid(.invalidAccount)
    }
    
    // 确认账号属性,""指定input为String,判断验证的账号是否和账号输入框相同,通过with传入account属性的value,返回.valid则符合条件,.invalid(FormError)则输出不符合日志。
    accountConfirm = ValidatingProperty("", with: account) { input, account in
        return input == account ? .valid : .invalid(.mismatchAccount)
    }
    
    // 同意用户协议可变属性,初始化为false
    termsAccepted = MutableProperty(false)
    
    // 构建联合属性,联结确认账号属性的结果和用户协议属性,并映射为字符串,根据条件返回映射结果。
    let validatedAccount : Property<String?> = Property.combineLatest(accountConfirm.result, termsAccepted).map { account, accepted -> String? in
        return !account.isInvalid && accepted ? account.value : nil
    }
    
    // 提交表单行为,展开联合属性validatedAccount,通过登录服务判断账号是否可用。
    submit = Action(unwrapping: validatedAccount) { (account : String) in
        let userAccount = account
        return loginService.canUseAccount(userAccount).promoteError(FormError.self).attemptMap{ Result<(), FormError> ($0 ? () : nil , failWith: .accountUnavaliable)}
    }
    
    // 错误信息,联合信号,在主线程中调用,返回结果映射组合为字符串,以备及时刷新到UI上。
    reasons = Property.combineLatest(account.result, accountConfirm.result).signal.debounce(0.1, on: QueueScheduler.main)
        .map{[$0, $1].flatMap{ $0.error?.reason }.joined(separator: "\n")}
}
...

第六步:创建视图并绑定数据

class ViewController: UIViewController {
    let loginService = LoginService()
    
    private var viewModel : LoginModel?
    
    @IBOutlet weak var accountField: UITextField!
    @IBOutlet weak var confirmField: UITextField!
    @IBOutlet weak var loginSwitch: UISwitch!
    @IBOutlet weak var loginBtn: UIButton!
    @IBOutlet weak var reasonLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        customizeModel()
        customizeSubviews()
    }

    func customizeModel() {
        viewModel = LoginModel(loginService: loginService)
        
        loginService.requestSignal.observeValues{
            print("UserService.requestSignal: Username `\($0)`.")
        }
        
        viewModel?.submit.completed.observeValues {
            let a = UIAlertController(title: "恭喜恭喜", message: "大吉大利,晚上吃鸡", preferredStyle: .alert)
            a.addAction(UIAlertAction(title: "吃!", style: .cancel, handler: nil))
            self.present(a, animated: true, completion: nil)
        }
        
        viewModel?.account.result.signal.observeValues {
            print("account: Validation result - \($0 != nil ? "\($0!)" : "No validation has ever been performed.")")
        }
        
        viewModel?.accountConfirm.result.signal.observeValues {
            print("accountConfirm: Validation result - \($0 != nil ? "\($0!)" : "No validation has ever been performed.")")
        }
    }
    
    func customizeSubviews () {
        accountField.text = viewModel?.account.value
        confirmField.text = viewModel?.accountConfirm.value
        loginSwitch.isOn = false
        
        viewModel!.account <~ accountField.reactive.continuousTextValues.skipNil()
        viewModel!.accountConfirm <~ confirmField.reactive.continuousTextValues.skipNil()
        viewModel!.termsAccepted <~ loginSwitch.reactive.isOnValues
        reasonLabel.reactive.text <~ viewModel!.reasons
        loginBtn.reactive.pressed = CocoaAction(viewModel!.submit)
    }
}

第七步:HAVE FUN!

上一篇下一篇

猜你喜欢

热点阅读