最好的文档还是官方文档
http://reactivecocoa.io/reactiveswift/docs/latest/basicoperators.html#catching-failures
Event
event
是枚举,其中包括值,成功失败以及中断几种情况
//Event.swift
public enum Event {
case value(Value)
case failed(Error)
case completed
case interrupted
}
需要注意的点: 当信号发送非Value的Event时, 那么这个信号就无效了. 无效的原因可能是failed(失败), completed(寿终正寝), interrupted(早就无效了).
Observer
event
是信息的载体, 而这里的observer
则是信息的处理逻辑封装
signal
基本使用
热信号,会主动把信号发出去。热信号是会一直发送,如果订阅者晚于信号发送时间,则无法收到
let signalTuple = Signal.pipe()
let (signal, observer) = Signal.pipe()
使用Signal.pipe()
来产生信号。这个函数会返回一个元组, 元组的第一个值是output(类型为Signal), 第二个值是input(类型为Observer). 我们通过output来订阅信号, 通过input来向信号发生信息.
//创建热信号。signal用于接收,innerObserver用于发送
let (signal, innerObserver) = Signal.pipe()
//为signal添加接受处理事件
signal.observeValues { (value) in
print("did received value: \(value)")
}
//为signal添加接受处理事件
signal.observeValues { (value) in
print("did received value: \(value)")
}
//发送者发送数据信息
innerObserver.send(value: 1)
//发送完成信号
innerObserver.sendCompleted()
额外补充:
//为signal添加接受处理事件
signal.observeValues { (value) in
print("did received value: \(value)")
}
innerObserver.send(value: 1)
在sendCompleted()
之后发送信号不会再被接收处理。同时,在send(value: 1)
之后添加的observeValues
也不会接收到之前的信号。
具体使用例子
定义信号
//产生信号并赋值给元组,使用indexSignal接收,使用indexObserver发送
let (indexSignal, indexObserver) = Signal.pipe()
发送信号
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let index = Int(scrollView.contentOffset.y / frame.size.width)
currentIndex = index
//发送信号,event为value
indexObserver.send(value: index)
//发送完成信号
indexObserver.sendCompleted()
}
接收处理信号
view.indexSignal.observeValues { (intValue) in
MSLog.logVerbose(message: "\(intValue)")
}
常用方法
- KVO
监听
//定义
public func signal(forKeyPath keyPath: String) -> Signal
//使用
dynamic var someValue = 0
reactive.signal(forKeyPath: "someValue").observeValues { (value) in
MSLog.logVerbose(message:"\(String(describing: value))")
}
tableView.reactive.signal(forKeyPath: "contentSize").observeValues {[weak self] (contentSize) in
if let contentSize = contentSize as? CGSize,
let strongSelf = self {
let isHidden = contentSize.height < strongSelf.tableView.height
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now(), execute: {
strongSelf.tableView.mj_footer.isHidden = isHidden
})
}
}
- Map
处理一个或多个信号
//定义(逃逸闭包可以不在本函数内部实现)
public func map(_ transform: @escaping (Value) -> U) -> Signal
//使用
let (signal, innerObserver) = Signal.pipe()
//使用map对值进行处理
signal.map { return "xxx" + $0}
.observeValues { (value) in
print(value)
}
- On
使用on做不需要参数的简单逻辑处理,是map的简化版
//定义(参数均有默认为空 可以不传)
public func on(
event: ((Event) -> Void)? = nil,
failed: ((Error) -> Void)? = nil,
completed: (() -> Void)? = nil,
interrupted: (() -> Void)? = nil,
terminated: (() -> Void)? = nil,
disposed: (() -> Void)? = nil,
value: ((Value) -> Void)? = nil
) -> Signal
//使用
signal.on().observeValues { (value) in
MSLog.logVerbose(message: value)
}
//具体格式
signal.on( value: { (value) in
MSLog.logVerbose(message: value)
}).observeValues { (value) in
MSLog.logVerbose(message: value)
}
- take(until:)
终止信号接收,传入终止信号
//定义
public func take(until trigger: Signal<(), NoError>) -> Signal
//使用
let (signal, innerObserver) = Signal.pipe()
//定义终止信号,注意信号第一个类型为()
let (takeSignal, takeObserver) = Signal<(), NoError>.pipe()
//传入终止信号参数 处理后续值
signal.take(until: takeSignal).observeValues { (value) in
MSLog.logVerbose(message: value)
}
innerObserver.send(value: "1")
innerObserver.send(value: "2")
//发送终止信号
takeObserver.send(value: ())
//不再被接收
innerObserver.send(value: "3")
- take(first:)
只接收前几次的信号传入,同理还有take(last:)
//定义
public func take(first count: Int) -> Signal
//使用
signal.take(first: 2).observeValues { (value) in
MSLog.logVerbose(message: value)
}
innerObserver.send(value: "1")
innerObserver.send(value: "2")
//不再被接受 只接收前两次
innerObserver.send(value: "3")
- merge
合并接受多个信号做统一处理,任一信号都能触发闭包中事件
//定义
public static func merge(signals: Sequence) -> Signal<_, _>
//使用
//3次信号发送均会被接收并且打印
let (signal1, innerObserver1) = Signal.pipe()
let (signal2, innerObserver2) = Signal.pipe()
let (signal3, innerObserver3) = Signal.pipe()
Signal.merge(signal1, signal2, signal3).observeValues { (value) in
MSLog.logVerbose(message: "\(value)")
}
innerObserver1.send(value: 1)
innerObserver1.sendCompleted()
innerObserver2.send(value: 2)
innerObserver2.sendCompleted()
innerObserver3.send(value: 3)
innerObserver3.sendCompleted()
- combineLatest
合并同时接受多个信号,多个信号同时发送才会被触发第一个闭包事件,此后其中任一信号改变均会触发闭包事件,闭包事件中的传入参数为多个信号值所组成的元组
//定义
public static func combineLatest(signals: Sequence) -> Signal<[_], _>
//使用
let (signal1, innerObserver1) = Signal.pipe()
let (signal2, innerObserver2) = Signal.pipe()
let (signal3, innerObserver3) = Signal.pipe()
Signal.combineLatest(signal1, signal2, signal3).observeValues { (tuple) in
MSLog.logVerbose(message: "\(tuple)")
}
innerObserver1.send(value: 1)
innerObserver2.send(value: 2)
innerObserver3.send(value: 3)
innerObserver1.send(value: 11)
innerObserver2.send(value: 22)
innerObserver3.send(value: 33)
innerObserver1.sendCompleted()
innerObserver2.sendCompleted()
innerObserver3.sendCompleted()
//打印结果
2019-04-02 16:29:33.141120+0800 MSProject_Swift[55271:9996243] (1, 2, 3)
2019-04-02 16:29:33.168205+0800 MSProject_Swift[55271:9996243] (11, 2, 3)
2019-04-02 16:29:33.169479+0800 MSProject_Swift[55271:9996243] (11, 22, 3)
2019-04-02 16:29:33.170184+0800 MSProject_Swift[55271:9996243] (11, 22, 33)
- zip
类似于上面的combineLatest
,不同之处在于:zip
为拉链式,只有当信号接收到的次数跟聚合的信号个数相同时才会触发一次闭包事件,以后每次也均是如此。闭包内参数仍为多个信号值所组成的元组
//定义
public static func zip(signals: Sequence) -> Signal<[_], _>
//使用
let (signal1, innerObserver1) = Signal.pipe()
let (signal2, innerObserver2) = Signal.pipe()
let (signal3, innerObserver3) = Signal.pipe()
Signal.zip(signal1, signal2, signal3).observeValues { (tuple) in
MSLog.logVerbose(message: "\(tuple)")
}
innerObserver1.send(value: 1)
innerObserver2.send(value: 2)
innerObserver3.send(value: 3)
innerObserver1.send(value: 11)
innerObserver2.send(value: 22)
innerObserver3.send(value: 33)
innerObserver1.send(value: 111)
innerObserver2.send(value: 222)
innerObserver1.sendCompleted()
innerObserver2.sendCompleted()
innerObserver3.sendCompleted()
//打印结果
2019-04-02 16:33:30.209404+0800 MSProject_Swift[55388:10001863] (1, 2, 3)
2019-04-02 16:33:30.211815+0800 MSProject_Swift[55388:10001863] (11, 22, 33)
SignalProducer
SignalProducer
是冷信号,不同于热信号的一直活动,需要一个唤醒操作,然后才能发送事件。就好比热信号是直播流,始终播放,不关心有没有订阅;而热信号就是视频文件,需要一个打开操作。这个操作就是start
系列方法。
//创建信号发射器producer
let producer = SignalProducer.init { (innerObserver, lifeTime) in
//信号声明周期结束后
lifeTime.observeEnded({
MSLog.logVerbose(message: "信号无效,在这里做清理工作")
})
//发送信号
innerObserver.send(value: 1)
innerObserver.send(value: 2)
innerObserver.sendCompleted()
}
//创建信号接收者,并对接收信号做处理
let outObserver = Signal.Observer.init({ (event) in
MSLog.logVerbose(message: "信号内容为 \(String(describing: event.value))")
})
//为信号发射器添加接受者,从此刻开始订阅信号
producer.start(outObserver)
//打印
2019-04-03 15:41:03.940507+0800 MSProject_Swift[62016:10345305] 信号内容为 Optional(1)
2019-04-03 15:41:03.942825+0800 MSProject_Swift[62016:10345305] 信号内容为 Optional(2)
2019-04-03 15:41:03.943917+0800 MSProject_Swift[62016:10345305] 信号内容为 nil
2019-04-03 15:41:03.944617+0800 MSProject_Swift[62016:10345305] 信号无效,在这里做清理工作
在网络请求中的实际应用
//模拟网络请求
func fetchData(completionHandler: (Int, Error?) -> ()) {
print("发起网络请求")
completionHandler(1, nil)
}
//创建信号发射器
let producer = SignalProducer.init { [weak self](innerObserver, _) in
self?.fetchData(completionHandler: { (data, error) in
//发射信号
innerObserver.send(value: data)
innerObserver.sendCompleted()
})
}
producer.startWithValues { (value) in
MSLog.logVerbose(message: "请求结果为\(value)")
}
producer.startWithValues { (value) in
MSLog.logVerbose(message: "请求结果为\(value)")
}
//打印为
发起网络请求
发起网络请求
2019-04-03 16:40:01.068884+0800 MSProject_Swift[63049:10410009] 请求结果为1
2019-04-03 16:40:01.072309+0800 MSProject_Swift[63049:10410009] 请求结果为1
这里会调用两次网络请求,所以在多处订阅的情况下并不推荐这样使用,可以使用如下方法。
let signalTuple = Signal.pipe()
signalTuple.output.observeValues { (value) in
MSLog.logVerbose(message: "请求结果为\(value)")
}
signalTuple.output.observeValues { (value) in
MSLog.logVerbose(message: "请求结果为\(value)")
}
self.fetchData { (data, error) in
signalTuple.input.send(value: data)
signalTuple.input.sendCompleted()
}
//打印
发起网络请求
2019-04-03 16:56:50.485567+0800 MSProject_Swift[63425:10430863] 请求结果为1
2019-04-03 16:56:50.488576+0800 MSProject_Swift[63425:10430863] 请求结果为1
2019-04-03 16:56:50.489538+0800
补充:
闭包内为防止循环引用,要设定[unowned self]
或者[weak self]
。更推荐用[weak self]
。
两者都能不增加引用计数打断循环引用,区别在于[unowned self]
可能会在self
释放后造成崩溃。而使用[weak self]
会自动生成self?
变为自判断链,避免造成崩溃。
MVVM实际应用
//ViewModel中
//信号发射器
var normalLoginProducer: SignalProducer, NoError> {
let producer = SignalProducer, NoError> { observer, _ in
//网络请求
let api = MSAPINormalLogin.init(with: "name", and: "password")
api.startWithCompletionBlock(success: { (request) in
//发送信号内容
observer.send(request.responseObject as! Signal, NoError>.Event)
observer.sendCompleted()
}, failure: { (request) in
// observer.send(error: request.error)
})
}
return producer
}
//信号接受处理
var normalLoginOberver: Signal, NoError>.Observer {
let outObserver = Signal, NoError>.Observer.init({ (event) in
//在此处对网络请求返回参数做处理
MSLog.logVerbose(message: "信号内容为 \(String(describing: event.value))")
//todo....
})
return outObserver
}
//controller中
viewModel.normalLoginProducer.start(viewModel. normalLoginOberver)
补充:
如果不需要在viewModel
中对网络请求结果进行进一步处理,则不需要normalLoginOberver
。同时如果需要回调参数则在控制器中直接执行producer.startWithValues
,反之执行producer.start()
。
Error
上述例子中均使用NoError
,但实际使用中需要对Error
进行处理。
APIError封装
import Foundation
import UIKit
import ReactiveCocoa
import ReactiveSwift
protocol ErrorRawValue {
var rawValue: Int { get }
}
enum TaskError: Int, ErrorRawValue {
case `default` = 101
case timeout
case canceled
case noNetwork
case noData
case noMoreData
}
extension Int: ErrorRawValue {
var rawValue: Int { return self }
}
struct APIError: Swift.Error {
let code: Int
var info = ""
var reason: String {
switch code {
case TaskError.noData.rawValue: return "空空如也~"
case TaskError.timeout.rawValue: return "请求超时~"
case TaskError.noNetwork.rawValue: return "未检测到网络连接~"
case TaskError.noMoreData.rawValue: return "没有更多了~"
default: return info.count > 0 ? info : "请求失败了~"
}
}
init(_ error: ErrorRawValue) {
self.code = error.rawValue
}
init(_ reason: String) {
self.code = TaskError.default.rawValue
self.info = reason
}
init(_ taskError: TaskError) {
self.code = taskError.rawValue
}
//封装请求结果字典
init(_ dictionary: Dictionary) {
let code: String = (dictionary["Code"] as? String) ?? "0"
self.code = Int.init(code)!
let info: String = (dictionary["Message"] as? String) ?? ""
self.info = info
}
static func == (left: APIError, right: ErrorRawValue) -> Bool {
return left.code == right.rawValue
}
static func != (left: APIError, right: ErrorRawValue) -> Bool {
return left.code != right.rawValue
}
}
extension SignalProducer where Error == APIError {
@discardableResult
func startWithValues(_ action: @escaping (Value) -> Void) -> Disposable {
return start(Signal.Observer(value: action))
}
}
结合YTKNetwork请求使用
- 发送
在viewModel
中网络请求失败情况下发送error
,同时解析网络请求错误信息封装进ApiError
failure: { (request) in
//发送失败内容
observer.send(error: APIError(request.responseObject as! Dictionary))
}
注意:
sendError
以失败方式结束信号,与sendCompleted
并列,以顺序先后为准。
- 接收
在viewModel
中对error
进行包装以及逻辑处理,实际上可以不用这步。
normalLoginProducer.flatMapError { (error: APIError) -> SignalProducer, APIError> in
self.apiError = error
return SignalProducer, APIError>.empty
}
- 处理
在ViewController
中直接对失败进行UI部分处理,例如弹出提示等
launchViewModel.normalLoginProducer.startWithFailed { (error) in
MSLog.logVerbose(message: error.reason)
}
- 完整请求处理过程
launchViewModel.normalLoginProducer.on(starting: {
MSLog.logVerbose(message: "正在开始")
}, started: {
MSLog.logVerbose(message: "已开始")
}, event: { (event) in
MSLog.logVerbose(message: "\(String(describing: event.value))")
}, failed: { (error) in
MSLog.logVerbose(message: "\(error.info)")
}, completed: {
MSLog.logVerbose(message: "已完成")
}, interrupted: {
MSLog.logVerbose(message: "已中断")
}, terminated: {
MSLog.logVerbose(message: "已终止")
}, disposed: {
MSLog.logVerbose(message: "已结束")
}).start(launchViewModel.normalLoginOberver)
MutableProperty
Property/MutableProperty只提供一种状态的事件
Property
property
所设定的值无法改变,所以并不常用。
let constant = Property(value: 1)
Property.value = 2 //error
MutableProperty
注意可以声明为常量,改变的为内部值。
let property = MutableProperty(1)
//冷信号可以收到初始值
property.producer.startWithValues { (value) in
MSLog.logVerbose(message: "producer recived \(value)")
}
//热信号无法收到初始值
property.signal.observeValues { (value) in
MSLog.logVerbose(message: "signal recived \(value)")
}
property.value = 2
//打印结果
2019-04-09 15:15:17.694053+0800 MSProject_Swift[98432:12387293] producer recived 1
2019-04-09 15:15:17.697001+0800 MSProject_Swift[98432:12387293] producer recived 2
2019-04-09 15:15:17.697951+0800 MSProject_Swift[98432:12387293] signal recived 2
使用
var errorText = MutableProperty("")
errorLabel.reactive.text <~ errorText
errorText.value = isValidPhoneNum ? "手机号格式不正确" : ""
拓展
备用
//UILabel的默认拓展
extension Reactive where Base: UILabel {
public var text: BindingTarget {
return makeBindingTarget { $0.text = $1 }//$0表示UI控件本身 $1表示value
}
public var attributedText: BindingTarget {
return makeBindingTarget { $0.attributedText = $1 }
}
}
//照猫画虎给YYLabel也加上拓展
extension Reactive where Base: YYLabel {
public var text: BindingTarget {
return makeBindingTarget { $0.text = $1 }//$0表示UI控件本身 $1表示value
}
public var attributedText: BindingTarget {
return makeBindingTarget { $0.attributedText = $1 }
}
}
注意:
<~
是将右侧数据源中的数据赋值给左侧。主要是用于viewModel
中的数据同步传递给vc
,而并非相反!
Action
前面几种都是单向传递,无论是"直播"还是"点播",接收者都无法干预其中,而action
则可以向其中传值。所以使用action
可以使viewController
向viewModel
中传递参数,而viewModel
是否采用以及如何处理则根据自己内部逻辑决定。
let action = Action { (input) -> SignalProducer in
MSLog.logVerbose(message: "input: \(input!)")
return SignalProducer({ (innerObserver, _) in
//发起网络请求
innerObserver.send(value: 1)
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2, execute: {
innerObserver.send(value: 2)
innerObserver.sendCompleted()
})
})
}
action.events.observe { event in MSLog.logVerbose(message: "did received Event: \(String(describing: event.value))") }
action.values.observeValues { value in MSLog.logVerbose(message: "did received Value: \(String(describing: value))") }
action.apply(["1": "xxx"]).start()
//打印结果
2019-04-09 15:52:47.165150+0800 MSProject_Swift[401:12439397] input: ["1": "xxx"]
2019-04-09 15:52:47.167398+0800 MSProject_Swift[401:12439397] did received Value: Optional(1)
2019-04-09 15:52:47.168281+0800 MSProject_Swift[401:12439397] did received Event: Optional(VALUE Optional(1))
两秒后
2019-04-09 15:52:49.165818+0800 MSProject_Swift[401:12439403] did received Value: Optional(2)
2019-04-09 15:52:49.167103+0800 MSProject_Swift[401:12439403] did received Event: Optional(VALUE Optional(2))
2019-04-09 15:52:49.168397+0800 MSProject_Swift[401:12439403] did received Event: Optional(COMPLETED)
2019-04-09 15:52:49.169428+0800 MSProject_Swift[401:12439403] did received Event: nil
注意:
订阅接收信号与start
发送信号的顺序不能错,否则会收不到value=1
时的信号。
Action
的三个泛型从左到右依次定义了输入类型, 输出类型, 错误类型, 通常可以使用typealias
简化。
使用action
传账号密码。
let param = ["name", "password"]
action.apply(param).start()
注意
同样可以使用action.apply(param).on().start()
描述网络请求各状态下HUD等UI细节。
Action结合MVVM的网络请求应用
vm中定义action
//可使用vm中属性作为请求参数,也可使用action中传参
let normalLoginAction = Action(execute: { (param: String) -> SignalProducer, APIError> in
var si = SignalProducer, APIError> { observer, _ in
let api = MSAPINormalLogin.init(with: "name", and: "password")
api.startWithCompletionBlock(success: { (request) in
let event = Signal, APIError>.Event.value(["AB" : param] as [String : Any])
//发送信号内容
observer.send(event)
observer.sendCompleted()
}, failure: { (request) in
//发送失败内容
})
}
//viewmodel中对数据进行处理
si = si.on(value: { (dict: Dictionary) in
//缓存等操作
MSLog.logVerbose(message: "---------")
MSLog.logVerbose(message: dict["AB"] as! String)
})
return si
})
vc中传参调用
//随时同步vm中密码属性
viewModel.password = passwordTextField.reactive.continuousTextValues
//apply传入参数,on回调各状态,start触发开始。
normalLoginAction.apply("123").on().startWithResult { (result: Result) in
//请求结果,可忽略。result为返回signalProducer类型,其中的value为vm返回值。
MSLog.logVerbose(message: "result is \(String(describing: result.value))")
}
CocoaAction
typealias TextAction = ReactiveSwift.Actionlet executeButton: UIButton
let enabled = MutableProperty(false)
enabled 0 }
let action = TextAction(enabledIf: enabled) { (input) -> APIProducer in
print("input: ", input) //这里的获取到的都是self.phoneNumberTF.text
return APIProducer({ (innerObserver, _) in
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5, execute: {
innerObserver.send(value: 1)
innerObserver.sendCompleted()
})
})
}
//通过CocoaAction给Button添加点击事件
executeButton.reactive.pressed = CocoaAction(action) {[unowned self] _ in
return self.phoneNumberTF.text //每次点击时都传入此时的phoneNumberTF.text作为action的输入
}
//初始化CocoaAction法1 input为一个固定值
let cocoaAction1 = CocoaAction(action, input: self.phoneNumberTF.text) //这样写 action得到的永远都是""
//let cocoaAction1 = CocoaAction(action, input: nil)
//初始化CocoaAction法2 input为一个非固定值
let cocoaAction2 = CocoaAction(action) { _ in
let input = "xxx" //各种操作得到一个输入
return input
}
实际应用代码:
//注意是左侧label中文字随vm中变动
registerView.errorEnsurePasswordLabel.reactive.text <~ registerViewModel.errorEnsurePasswordText
//可以在比包内传参,也可以不传
registerView.registerButton.reactive.pressed = ButtonAction(registerViewModel.registerAction){}
//执行中HUD
registerViewModel.registerAction.isExecuting.signal.observeValues { (isExecuting) in
isExecuting ? HUD.show(.progress) : PKHUD.sharedHUD.hide()
}
//错误toast
registerViewModel.registerAction.errors.observeValues {[weak self] (error) in
self?.view.makeToast(error.reason)
}
//成功传值回调
registerViewModel.registerAction.values.observeValues {[weak self] (value) in
self?.view.makeToast("回调值为:\(String(describing: value))")
}
//成功回调
registerViewModel.registerAction.completed.observeValues {[weak self] (value) in
self?.view.makeToast("注册成功")
}
拦截方法
冷信号与热信号
signal
为热信号,对应RACSubject
signalProducer
为冷信号,对应RACSignal
热信号类似“直播”,错过了就不再处理。而冷信号类似“点播”,每次订阅都会从头开始。
基本
self.reactive.trigger(for: #selector(viewWillAppear(_:))) observeValues { () in
print("viewWillAppear被调用了")
}
拦截自定义方法
reactive.trigger(for: #selector(customMethod)).observeValues {
print("customMethod")
}
dynamic func customMethod(){
print("a custom method")
}
为什么Swift中要使用关键字dynamic。Swift 中的函数可以是静态调用,静态调用会更快。Swift的代码直接被编译优化成静态调用的时候,就不能从Objective-C 中的SEL字符串来查找到对应的IMP了。
纯Swift类没有动态性,但在方法、属性前添加dynamic修饰可以获得动态性。该修饰符用于修饰任何兼容 Objective-C 的类的成员。访问被 dynamic 修饰符标记的类成员将总是由 Objective-C 运行时系统进行动态派发,而不会由编译器进行内联或消虚拟化。
使用dynamic
关键字使方法具有runtime
动态特征,以使用Method Swizzling
注意:
//trigger 作为开关 只能返回开关单纯信号 Signal<(), NoError>
public func trigger(for selector: Selector) -> Signal<(), NoError>
//signal 作为信号 可以夹带参数数组 Signal<[Any?], NoError>
public func signal(for selector: Selector) -> Signal<[Any?], NoError>
//拦截方法 并解析参数
let signal = reactive.signal(for: #selector(customMethod(str:b:)))
signal.observeValues { value in
let str = value.first as! String
let bool = value.last as! Bool
print("first member is \(str) , second member is \(bool)")
}