ReactiveCocoa
配置安裝RAC
- Carthage 安裝
項(xiàng)目目錄下執(zhí)行
$ touch Cartfile
$ vim Cartfile
并直接寫(xiě)入Cartfile
github "ReactiveCocoa/ReactiveCocoa" ~> 6.0
保存后直接執(zhí)行,注意我的版本,可能與實(shí)際的版本有差別。
$ 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 會(huì)自動(dòng)下載對(duì)應(yīng)的frameworks,主要有:
ReactiveCocoa、ReactiveSwift、Result三個(gè)對(duì)應(yīng)框架
下載完成后參考我的另外一篇carthage集成添加到項(xiàng)目中。
- Cocoapods 安裝
項(xiàng)目目錄下執(zhí)行
$ touch Podfile
$ vim Podfile
并直接寫(xiě)入Podfile
platform:ios, '8.0'
use_frameworks!
target "yourtarget" do
...
pod 'ReactiveCocoa','~>6.0'
end
保存后直接執(zhí)行,注意我的版本,可能與實(shí)際的版本有差別。
$ pod install
...
swift 中使用 RAC + MVVM
第一步:引入頭文件
import Result
import ReactiveSwift
import ReactiveCocoa
第二步:創(chuàng)建服務(wù)及信號(hào)管道
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()
}
}
}
第三步:創(chuàng)建ViewModel
創(chuàng)建LoginModel類(lèi),并添加構(gòu)造方法傳入服務(wù)
class LoginModel {
// 添加Error子類(lèi),F(xiàn)ormError
struct FormError : Error {
let reason : String
static let invalidAccount = FormError(reason: "請(qǐng)輸入正確的賬號(hào)")
static let mismatchAccount = FormError(reason: "賬號(hào)輸入不匹配")
static let accountUnavaliable = FormError(reason: "賬號(hào)已存在")
}
init(loginService : LoginService) {
}
}
第四步:在ViewModel中添加屬性、行為和信號(hào)量
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的屬性、行為和信號(hào)量
...
init(loginService : LoginService) {
// 賬號(hào)屬性,""指定input為String,判斷賬號(hào)是否有效,返回.valid則符合條件,.invalid(FormError)則輸出不符合日志。
account = ValidatingProperty("") { input in
return input.hasSuffix("aha") ? .valid : .invalid(.invalidAccount)
}
// 確認(rèn)賬號(hào)屬性,""指定input為String,判斷驗(yàn)證的賬號(hào)是否和賬號(hào)輸入框相同,通過(guò)with傳入account屬性的value,返回.valid則符合條件,.invalid(FormError)則輸出不符合日志。
accountConfirm = ValidatingProperty("", with: account) { input, account in
return input == account ? .valid : .invalid(.mismatchAccount)
}
// 同意用戶協(xié)議可變屬性,初始化為false
termsAccepted = MutableProperty(false)
// 構(gòu)建聯(lián)合屬性,聯(lián)結(jié)確認(rèn)賬號(hào)屬性的結(jié)果和用戶協(xié)議屬性,并映射為字符串,根據(jù)條件返回映射結(jié)果。
let validatedAccount : Property<String?> = Property.combineLatest(accountConfirm.result, termsAccepted).map { account, accepted -> String? in
return !account.isInvalid && accepted ? account.value : nil
}
// 提交表單行為,展開(kāi)聯(lián)合屬性validatedAccount,通過(guò)登錄服務(wù)判斷賬號(hào)是否可用。
submit = Action(unwrapping: validatedAccount) { (account : String) in
let userAccount = account
return loginService.canUseAccount(userAccount).promoteError(FormError.self).attemptMap{ Result<(), FormError> ($0 ? () : nil , failWith: .accountUnavaliable)}
}
// 錯(cuò)誤信息,聯(lián)合信號(hào),在主線程中調(diào)用,返回結(jié)果映射組合為字符串,以備及時(shí)刷新到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")}
}
...
第六步:創(chuàng)建視圖并綁定數(shù)據(jù)
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)
}
}