ReactiveCocoa
配置安装RAC
- Carthage 安装
项目目录下执行
$ 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,主要有:
ReactiveCocoa
、ReactiveSwift
、Result
三个对应框架
下载完成后参考我的另外一篇carthage集成添加到项目中。
- Cocoapods 安装
项目目录下执行
$ 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)
}
}