Swift中的RactiveCocoa (上)

RXSwift的教程太多, ReactiveSwift的教程又太少


简书账号停止维护, 提问/讨论请移步掘金账号

前言

大概是这样, Swift4.0出了, 重新梳理Swift知识, 对比了下RXSwift和ReactiveSwift, 喜欢ReactiveSwift多一些, 想了想, 出份基础教程.
建议新人朋友只看如何使用, 至于实现概述看看最后的总结和图了解一下思路就行了.

目录

  • Event
  • Observer
  • Signal
  • SignalProducer
  • Property/MutableProperty
  • Action/CocoaAction
  • Hello ReactiveSwift

Event

在ReactiveSwift中, 信息的载体(value/error)对应的是一个枚举Event, Event的定义如下:

//Event.swift
public enum Event {
        case value(Value)
        case failed(Error)
        case completed
        case interrupted
}

前三个状态顾名思义就不解释了, 第四个状态interrupted表示事件被打断, 除非你去订阅一个已经无效的信号, 否则这个状态不会出现, 所以不用太多关注这个状态.

需要注意的点: 当信号发送非Value的Event时, 那么这个信号就无效了. 无效的原因可能是failed(失败), completed(寿终正寝), interrupted(早就无效了).

Observer

上面说过Event是信息的载体, 而这里的Observer则是信息的处理逻辑封装. Observer的主要代码如下:

//Observer.swift
public final class Observer {
        public typealias Action = (Event) -> Void
        private let _send: Action

        public init(_ action: @escaping Action) {
            self._send = action
            ...
        }

        public func send(_ event: Event) {
            _send(event)
        }
        public func send(value: Value) {
            _send(.value(value))
        }
        public func sendXXX() //其实都是send(_ event: Event)
}

容易看到, Observer内部保持了一个处理Event的闭包, 初始化Observer就是在设置这个闭包, 而调用Observer.send则是在执行这个闭包.

需要注意的点: Observer封装了Event的处理逻辑.

Signal

有了信息的载体和信息的处理逻辑, 接下来需要的是: 将信息发送出去.
在ReactiveSwift中, 想要发送信息共有四种途径, 这里我们先介绍第一种: Signal.(事实上, 四种途径最终都是通过Signal来完成的, 所以, 其实只有一种.)

Signal是ReactiveSwift中热信号的实现, "热"的意思是它是一直活动着的, 会主动将产出的事件Event向外发送, 而不会等到有人订阅后才开始发送. 这意味着如果订阅的时机晚于发送的时机, 那么订阅者是不会收到订阅时机之前的事件的.
举个栗子: 春晚现场直播从晚8点一直播到12点, 这段时间产出的节目就是Value事件, 12点一到产出的就是Completed事件. 很明显, 不管有没有人看春晚, 春晚现场都不关心, 节目来了就上, 时间一到就散. 但如果你想看直播, 最好的时机当然是8点, 若是9点才打开电视, 那9点之前的节目你肯定就错过了.

概念讲完了, 我们来看看代码, 这里我会分成两个部分: Signal的使用和Signal的实现概述, 大家主要关注使用部分即可.

  • Signal的使用
note: 这里的Value和Error都是泛型, 你需要在创建的时候进行指定
//public static func pipe(disposable: Disposable? = nil) -> (output: Signal, input: Observer)

let signalTuple = Signal<Int, NoError>.pipe()
let (signal, observer) = Signal<Int, NoError>.pipe()
...

通常, 你应该只通过Signal.pipe()函数来初始化一个热信号. 这个函数会返回一个元组, 元组的第一个值是output(类型为Signal), 第二个值是input(类型为Observer). 我们通过output来订阅信号, 通过input来向信号发生信息.

需要注意的点: output的作用是管理信号状态并保存由订阅者提供的Observer对象(Observer._send封装了Event的处理逻辑), 而input的作用则是在接收到Event后依次执行这些被保存的Observer._send.
来看一段订阅Signal的基础代码:

override func viewDidLoad() {
        super.viewDidLoad()
        
        //1.创建signal(output)和innerObserver(input)
        let (signal, innerObserver) = Signal<Int, NoError>.pipe()
        
        //2.创建Observer
        let outerObserver1 = Signal<Int, NoError>.Observer(value: { (value) in
            print("did received value: \(value)")
        })
        //2.还是创建Observer
        let outerObserver2 = Signal<Int, NoError>.Observer { (event) in
            switch event {
            case let .value(value):
                print("did received value: \(value)")
            default: break
            }
        }
        
        signal.observe(outerObserver1)//3.向signal中添加Observer
        signal.observe(outerObserver2)//3.还是向signal中添加Observer
        
        innerObserver.send(value: 1)//4.向signal发生信息(执行signal保存的所有Observer对象的Event处理逻辑)
        innerObserver.sendCompleted()//4.还是执向signal发生信息
}
//输出:  did received value: 1
         did received value: 1

这段代码简单的演示了如何创建并订阅一个Signal, 但事实上, 实际开发中我们肯定不会这样写, 太繁琐了. 它的意义在于告诉各位: 1)每订阅一次Signal实际上就是在向Signal中添加一个Observer对象. 2)即使每次订阅信号的处理逻辑都是一样的, 但它们仍然是完全不同的的两个Observer对象.
我们来把上面的代码改的简洁一点:

typealias NSignal<T> = ReactiveSwift.Signal<T, NoError>
override func viewDidLoad() {
        super.viewDidLoad()
        //1.创建signal(output)和innerObserver(input)
        let (signal, innerObserver) = NSignal<Int>.pipe()
        
        signal.observeValues { (value) in   //2&3.创建Observer并添加到Signal中
            print("did received value: \(value)")
        }
        signal.observeValues { (value) in   //2&3.还是创建Observer并添加到Signal中
            print("did received value: \(value)")
        }
        
        innerObserver.send(value: 1) //4. ...
        innerObserver.sendCompleted() //4. ...
}

例子很简单, 主要介绍下Signal.observeValues, 这是Signal.observe的一个便利函数, 作用是创建一个只处理Value事件的Observer并添加到Signal中, 类似的还有只处理Failed事件的Signal.observeFailed和所有事件都能处理的Signal.observeResult.

吐槽: ReactiveSwift中职责区分十分明确, 这意味着做一件简单的事情可能会需要多个部件共同协作, 这对使用者来说比较繁琐, 所以ReactiveSwift提供了很多的便利函数来进行简化, 但过多的便利函数密密麻麻一堆让人看得心烦, 于是被刚接触的朋友们吐槽"复杂", "不简洁". 但通常我们只需要记着几个常用函数即可做好大部分事情, 其实ReactiveSwift非常简洁.

回到代码来, 接下来介绍下"热"信号的相关代码:

typealias NSignal<T> = ReactiveSwift.Signal<T, NoError>
//ViewModel.swift
class ViewModel {
    let signal: NSignal<Int>
    let innerObserver: NSignal<Int>.Observer
    
    init() { (signal, innerObserver) = NSignal<Int>.pipe() }
}

//View1.swift
class View1 {
    func bind(viewModel: ViewModel) {
        viewModel.signal.observeValues { (value) in
            print("View1 received value: \(value)")
        }
    }
}

//View2.swift
class View2 {
    func bind(viewModel: ViewModel) {
        viewModel.signal.observeValues { (value) in
            print("View2 received value: \(value)")
        }
    }
}

//View3.swift
class View3 {
    func bind(viewModel: ViewModel) {
        viewModel.signal.observeValues { (value) in
            print("View3 received value: \(value)")
        }
        viewModel.signal.observeInterrupted {
            print("View3 received interrupted")
        }
    }
}

override func viewDidLoad() {
        super.viewDidLoad()
        
        let view1 = View1()
        let view2 = View2()
        let view3 = View3()
        let viewModel = ViewModel()

        view1.bind(viewModel: viewModel)//订阅时机较早
        viewModel.innerObserver.send(value: 1)

        view2.bind(viewModel: viewModel)//订阅时机较晚
        viewModel.innerObserver.send(value: 2)
        viewModel.innerObserver.sendCompleted()//发送一个非Value事件 信号无效
        
        view3.bind(viewModel: viewModel)//信号无效后才订阅
        viewModel.innerObserver.send(value: 3)//信号无效后发送事件
    }
输出: View1 received value: 1
      View1 received value: 2
      View2 received value: 2
      View3 received interrupted

这里我们可以看到, view2的订阅时间晚于value1的发送时间, 所以view2收不到value1对应的事件, 这部分对应上面我说的热信号并不关心订阅者的情况, 一旦有事件即会发送.
第二部分则是Signal自身的特性: 收到任何非Value的事件后信号便无效了. 所以你会看到虽然view1和view2的订阅都早于value3的发送时间, 但因为value3在信号发送前先发送了completed事件, 所以view1和view2都不会收到value3事件, 同理, view3也不会收到value3事件(它只会收到一个interrupted, 如果它关心的话).

接下来介绍一些Signal常用的函数, 这些函数会在文末的demo中出现.

  • KVO
public func signal(forKeyPath keyPath: String) -> Signal<Any?, NoError>
let tableView: UITableView
dynamic var someValue = 0

reactive.signal(forKeyPath: "someValue").observeValues { [weak self] (value) in
      //code
}

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
        })
    }
}

KVO的Reactive版本, 对于NSObject的子类可以直接使用, 对于Swift的原生类需要加上dynamic修饰.

  • map
let (signal, innerObserver) = NSignal<Int>.pipe()
signal.map { return "xxx" + String($0) } //map就不解释了
.observeValues { (value) in
            print(value)
        }

innerObserver.send(value: 1)
innerObserver.sendCompleted()
输出: xxx1
  • on
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<Value, Error>
let (signal, innerObserver) = NSignal<Int>.pipe()

signal.on( value: { (value) in
    print("on value: \(value)")
}).observeValues { (value) in
    print("did received value: \(value)")
}

innerObserver.send(value: 1)
innerObserver.sendCompleted()
输出: on value: 1
      did received value: 1

on: 在信号发送事件和订阅者收到事件之间插入一段事件处理逻辑, 你可以把它看做map的简洁版. (这个函数的参数很多, 但默认都有给nil, 所以你只需要关心自己需要的部分即可, 比如这里我只想在Value事件间插入逻辑)

  • take(until:)
public func take(until trigger: Signal<(), NoError>) -> Signal<Value, Error>
let (signal, innerObserver) = NSignal<Int>.pipe()
let (takeSignal, takeObserver) = NSignal<()>.pipe()

signal.take(until: takeSignal).observeValues { (value) in
    print("received value: \(value)")
}

innerObserver.send(value: 1)
innerObserver.send(value: 2)

takeObserver.send(value: ())
innerObserver.send(value: 3)

takeObserver.sendCompleted()
innerObserver.sendCompleted()

输出: received value: 1
      received value: 2

take(until:): 在takeSignal发送Event之前, signal可以正常发送Event, 一旦takeSignal开始发送Event, signal就停止发送, takeSignal相当于一个停止标志位.

  • take(first:)
public func take(first count: Int) -> Signal<Value, Error>
let (signal, innerObserver) = NSignal<Int>.pipe()
signal.take(first: 2).observeValues { (value) in
    print("received value: \(value)")
}

innerObserver.send(value: 1)
innerObserver.send(value: 2)
innerObserver.send(value: 3)
innerObserver.send(value: 4)

innerObserver.sendCompleted()
输出: received value: 1
      received value: 2

take(first:): 只取最初N次的Event.
类似的还有signal.take(last: ): 只取最后N次的Event.

  • merge
public static func merge(_ signals: Signal<Value, Error>...) -> Signal<Value, Error>

let (signal1, innerObserver1) = NSignal<Int>.pipe()
let (signal2, innerObserver2) = NSignal<Int>.pipe()
let (signal3, innerObserver3) = NSignal<Int>.pipe()

Signal.merge(signal1, signal2, signal3).observeValues { (value) in
    print("received value: \(value)")
}

innerObserver1.send(value: 1)
innerObserver1.sendCompleted()

innerObserver2.send(value: 2)
innerObserver2.sendCompleted()

innerObserver3.send(value: 3)
innerObserver3.sendCompleted()
输出: received value: 1
     received value: 2
     received value: 3

merge: 把多个信号合并为一个新的信号,任何一个信号有Event的时就会这个新信号就会Event发送出来.

  • combineLatest
public static func combineLatest<S: Sequence>(_ signals: S) -> Signal<[Value], Error>
let (signal1, innerObserver1) = NSignal<Int>.pipe()
let (signal2, innerObserver2) = NSignal<Int>.pipe()
let (signal3, innerObserver3) = NSignal<Int>.pipe()

Signal.combineLatest(signal1, signal2, signal3).observeValues { (tuple) in
    print("received value: \(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()

输出: received value: (1, 2, 3)
      received value: (11, 2, 3)
      received value: (11, 22, 3)
      received value: (11, 22, 33)

combineLatest: 把多个信号组合为一个新信号,新信号的Event是各个信号的最新的Event的组合.
"组合"意味着每个信号都至少有发送过一次Event, 毕竟组合的每个部分都要有值. 所以, 如果有某个信号一次都没有发送过Event, 那么这个新信号什么也不会发送, 不论其他信号发送了多少Event.
另外, 新信号只会取最新的Event的来进行组合, 而不是数学意义上的组合.

  • zip
public static func zip<S: Sequence>(_ signals: S) -> Signal<[Value], Error>
let (signal1, innerObserver1) = NSignal<Int>.pipe()
let (signal2, innerObserver2) = NSignal<Int>.pipe()
let (signal3, innerObserver3) = NSignal<Int>.pipe()

Signal.zip(signal1, signal2, signal3).observeValues { (tuple) in
        print("received value: \(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()

输出: received value: (1, 2, 3)
      received value: (11, 22, 33)

zip: 新信号的Event是各个信号的最新的Event的进行拉链式组合.
有人把这个叫压缩, 但我觉得拉链式组合更贴切一些. 拉链的左右齿必须对齐才能拉上, 这个函数也是一样的道理. 只有各个信号发送Event的次数相同(对齐)时, 新信号才会发送组合值. 同理, 如果有信号未发送那么什么也不会发生.

我个人常用的Signal的相关函数就是这些, 其他的自己不怎么用的函数就不介绍了, 接下来介绍下Signal的实现概述, 不关心的朋友请直接跳过.

  • Signal的实现概述

让我们点开Signal.swift, 你应该会看到Signal只有一个孤零零的core属性一个设置core属性的初始化函数以及一个调用core.observe的observe函数. 正如源码注释中所说: Signal只是Core的一个壳. 所以接下来我们主要介绍的其实是Core. 我们来看看Core的主要定义:

private final class Core {
        private let disposable: CompositeDisposable
        private let stateLock: Lock //状态锁
        private let sendLock: Lock //事件锁

        private var state: State //信号状态 最重要就是它了
        ...略
}

private enum State {
        case alive(Bag<Observer>, hasDeinitialized: Bool)
        case terminating(Bag<Observer>, TerminationKind)
        case terminated
}

public struct Bag<Element> {//Bag可以认为是数组的一层封装
        fileprivate var elements: ContiguousArray<Element> = []
    ...略
}

Core里面最重要的属性就是这个State. State的作用有两个: 一个是指示信号的状态, 另一个就是上文我提到过的保存信号订阅者添加进来的Observer对象.
我们来看看添加Observer对象部分的代码:

fileprivate func observe(_ observer: Observer) -> Disposable? {
    var token: Bag<Observer>.Token?
    
    stateLock.lock()
    
    //1. 信号处于state.alive状态 将新的observer对象添加到state.alive的数组中
    if case let .alive(observers, hasDeinitialized) = state {
        var newObservers = observers
        token = newObservers.insert(observer)
        self.state = .alive(newObservers, hasDeinitialized: hasDeinitialized)
    }
    
    stateLock.unlock()
    
    //2. 如果1顺利执行 token会被赋值(即信号处于alive状态) 返回一个Disposable对象 否则直接向observer对象发送interrupted事件
    if let token = token {
        return AnyDisposable { [weak self] in
            self?.removeObserver(with: token)
        }
    } else {
        observer.sendInterrupted()
        return nil
    }
}

上面我说过Observer是Event的处理逻辑封装, 这里我们添加并保存了Observer(也就是保存了Event的处理逻辑), 接下来需要的就是在合适的时机执行这些Observer内部的处理逻辑. 这部分代码对应Core.send(_ event: Event):

private func send(_ event: Event) {
    ...lock部分代码 略
    if event.isTerminating {

        //1. 收到非Value的Event, 将信号状态从.alive切换到.terminating 并将.alive中的Observer数组移到.terminating中(如果此时信号处于.alive的话)
        if case let .alive(observers, _) = state {
            self.state = .terminating(observers, .init(event))
        }
        //2. 依次执行.terminating的数组中Observer.send函数(如果有的话)
        tryToCommitTermination()
    } else {
        
        //1. 收到Value的Event, 依次执行.alive的数组中Observer.send函数(如果此时信号处于.alive的话)
        if case let .alive(observers, _) = self.state {
            for observer in observers {
                observer.send(event)
            }
        }
        
        //2. 收到Value的Event 依次执行.terminating的数组中Observer.send函数(如果此时信号处于.terminating的话)
        if case .terminating = state {
            tryToCommitTermination()
        }
    }
}

private func tryToCommitTermination() {
    ...lock部分代码 略
    if case let .terminating(observers, terminationKind) = state {

        //1.切换状态到.terminated 
        //2.依次执行.terminating的数组中Observer.send函数(如果有的话)
        state = .terminated
        if let event = terminationKind.materialize() {
            for observer in observers {
                observer.send(event)
            }
        }
    }
}

我去掉了Core.send函数和lock相关的代码, 这样看起来会简单些, 大家对照注释应该比较好理解, 这里我主要说说State的切换. 先把State定义贴过来:

private enum State {
        case alive(Bag<Observer>, hasDeinitialized: Bool)
        case terminating(Bag<Observer>, TerminationKind)
        case terminated
        ...一些函数 略
}

可以看到第三个状态terminated是不带关联数组的, 这意味着当信号切换到terminated状态时, 那么那些被保存的Observer对象也就跟着释放了, 所以, 当不再需要使用信号时, 总是应该向信号发送一个非Value事件确保资源释放.

事实上, 在ReactiveSwift5.0中Signal和State会保持一个引用环确保常驻内存, 如果你不发送非Value事件的话, 资源是不会被释放的. 但在最新的ReactiveSwift中, 这个引用环被去掉了, 你不用担心资源释放的问题. 但不再需要信号的时候调用一下sendCompleted()总是一个好习惯.

现在我们知道了信号状态的切换, Observer的添加, Observer.send的执行, 那么最后就只剩下将这一切连接起来了, 显然, 这个连接函数就是Signal.pipe():

//Signal.pipe()
public static func pipe(disposable: Disposable? = nil) -> (output: Signal, input: Observer) {
    var observer: Observer!
    
    let signal = self.init { innerObserver, lifetime in
        observer = innerObserver
        lifetime += disposable
    }
    return (signal, observer)
}
//Signal.init()
public init(_ generator: (Observer, Lifetime) -> Void) {
     core = Core(generator)
}

//Core.init()
fileprivate init(_ generator: (Observer, Lifetime) -> Void) {
    
    //1. 设置信号初始状态为alive 同时初始化alive中的数组
    state = .alive(Bag(), hasDeinitialized: false)

    ...初始化lock 略
   
    //2. 创建一个Observer对象并将该对象的_send闭包设置为Core.send函数
    generator(Observer(action: self.send, interruptsOnDeinit: true), Lifetime(disposable))
}

这里我们看到, pipe()函数会通过一个generator: (Observer, Lifetime)闭包去创建Core对象, 然后通过这个Core对象去创建Signal, pipe()函数通过generator闭包捕获了Core.init()中的innerObserver对象, 而这个InnerObserver对象的_send指向的其实是Core.send函数. 最后pipe()将创建完成Signal和InnerObserver打包返回.
这也是为什么上文我说: pipe().output(即Signal)的作用是管理信号状态并保存由订阅者提供的Observer对象, 而pipe().input(即InnerObserver)的作用则是在接收到Event后依次执行这些被保存的Observer._send.
我们把上面的流程用一张图来表示:


Signal.png

这张图其实也可用来标示OC的RACSubject或者RXSwift的RXSubject的大体工作流程, 毕竟三者都是State, 观察者数组, send函数, observer/subscribe函数, 只是加不加壳罢了.

SignalProducer

SignalProducer是ReactiveSwift中冷信号的实现, 是第二种发送事件的途径.

上文说到热信号是活动着的事件发生器, 相对应的, 冷信号则是休眠中的事件发生器. 也就是说冷信号需要一个唤醒操作, 然后才能发送事件, 而这个唤醒操作就是订阅它. 因为订阅后才发送事件, 显然, 冷信号不存在时机早晚的问题. 仍以春晚举例:
冷信号相当于春晚的视频文件而不是现场直播, 正常情况下, 视频文件肯定是不会自动播放的, 但你只要一双击, 它就被启动播放然后输出节目了.
照例, 我还是给出两份代码, 大家懂个意思就行:

//1. 通过SignalProducer.init(startHandler: (Observer, Lifetime) -> Void)创建SignalProducer
let producer = SignalProducer<Int, NoError> { (innerObserver, lifetime) in
    lifetime.observeEnded({
        print("信号无效了 你可以在这里进行一些清理工作")
    })
    
    //2. 向外界发送事件
    innerObserver.send(value: 1)
    innerObserver.send(value: 2)
    innerObserver.sendCompleted()
}

//3. 创建一个观察者封装事件处理逻辑
let outerObserver = Signal<Int, NoError>.Observer(value: { (value) in
    print("did received value: \(value)")
})
//4. 添加观察者到SignalProducer
producer.start(outerObserver)

输出: did received value: 1
     did received value: 2
信号无效了 你可以在这里进行一些清理工作
typealias Producer<T> = ReactiveSwift.SignalProducer<T, NoError>
let producer = Producer<Int> { (innerObserver, _) in
    //没什么想清理的
    
    innerObserver.send(value: 1)
    innerObserver.send(value: 2)
    innerObserver.sendCompleted()
}
producer.startWithValues { (value) in
    print("did received value: \(value)")
}
producer.startWithFailed(action: )
producer.startWithResult(action: )
producer.startWithXXX...各种便利函数

和Signal的订阅方式如出一辙, 只是名字换了一下, Signal.observeXXX换成了SignalProducer.startXXX.
大家都是事件发生器, 所以API方面Signal和SignalProducer都是一样的, 上面的map, on, merge, comblinelast...等等, SignalProducer也有一份, 作用也都一样, 我就不多说了, 这里简单给两段代码说说可能遇到的坑.

func fetchData(completionHandler: (Int, Error?) -> ()) {
    print("发起网络请求")
    completionHandler(1, nil)
}

let producer = Producer<Int> {[unowned self] (innerObserver, _) in
    self.fetchData(completionHandler: { (data, error) in
        innerObserver.send(value: data)
        innerObserver.sendCompleted()
    })
}
producer.startWithValues { (value) in
    print("did received value: \(value)")
}
producer.startWithValues { (value) in
    print("did received value: \(value)")
}

输出: 发起网络请求
      did received value: 1
      发起网络请求
      did received value: 1

也许你只是想两个观察者共享一次网络请求带回的Event, 但事实上这里会发生两次网络请求, 但这不是一个bug, 这是一个feature.
SignalProducer的一个特性是, 每次被订阅就会执行一次初始化时保存的闭包. 所以如果你有类似一次执行, 多处订阅的需求, 你应该选择Signal而不是SignalProducer. 所以, 符合需求的代码可能是这样:

let signalTuple = NSignal<Int>.pipe()

signalTuple.output.observeValues { (value) in
    print("did received value: \(value)")
}
signalTuple.output.observeValues { (value) in
    print("did received value: \(value)")
}

self.fetchData { (data, error) in
    signalTuple.input.send(value: data)
    signalTuple.input.sendCompleted()
}

输出: 发起网络请求
     did received value: 1
     did received value: 1

到目前为止, 示例代码中给到的都是NoError类型的信号, 在实际开发中, 这显然是不可能的, 毕竟错误是不可避免的. 通常我们的项目会声明一个类似APIError的错误类型来表示这些错误, 所以你可能会有这样的声明:

struct APIError: Swift.Error {
    
    let code: Int
    var reason = ""
}

typealias NSignal<T> = ReactiveSwift.Signal<T, NoError>
typealias APISignal<T> = ReactiveSwift.Signal<T, APIError>

typealias Producer<T> = ReactiveSwift.SignalProducer<T, NoError>
typealias APIProducer<T> = ReactiveSwift.SignalProducer<T, APIError>

这样的声明很好, 能让ReactiveSwift写起来像RXSwift一样"简洁". 但这里需要加上下面的代码才能更好的工作:

extension SignalProducer where Error == APIError {
    
    @discardableResult
    func startWithValues(_ action: @escaping (Value) -> Void) -> Disposable {
        return start(Signal.Observer(value: action))
    }
}

这是因为默认的SignalProducer是没有startWithValues函数的, ReactiveSwift会在Extension里给它加上startWithValues函数, 但是这只对NoError有效, 所以当你在自定义Error时, 请记得加上类似的代码.

基本使用介绍完了, 照例是SignalProducer的实现概述, 不关心朋友的请直接跳过.

  • SignalProducer的实现概述

和Signal类似, SignalProducer也是一个壳, 壳的内部装着的是SignalProducerCore, 这个类有三个子类SignalCore, GeneratorCore和TransformerCore. 其中SignalCore和GeneratorCore用于普通操作的SignalProducer, 而TransformerCore则是在map, take, filterMap...之类的操作才会用上. 这里我主要介绍使用率较高的SignalCore, 下面看看它的定义:

//SignalProducerCore
internal class SignalProducerCore {
    
    //Instance作用: 
    //1.持有一个热信号Signal 用于保存订阅者添加的Observer对象
    //2.持有一个() -> Void闭包 用于执行回调(对子类SignalCore来说 这个闭包的作用则是向上面的Signal.core.state.Observes数组发送Event)
    struct Instance {
        let signal: Signal<Value, Error>
        let observerDidSetup: () -> Void
        let interruptHandle: Disposable
    }

    //抽象方法 留待子类实现
    func makeInstance() -> Instance {
        fatalError()
    }
    //抽象方法 留待子类实现(对子类SignalCore来说 这个函数就是订阅Signal或者是添加Observer对象到Signal中)
    func start(_ generator: (_ upstreamInterruptHandle: Disposable) -> Signal.Observer) -> Disposable {
        fatalError()
    }
    ...和TransformerCore相关的部分 略
}

//SignalCore
private final class SignalCore: SignalProducerCore {
    private let _make: () -> Instance

    //这个action会由SignalProducer传入
    init(_ action: @escaping () -> Instance) {
        self._make = action
    }

    //当外部执行SignalProducer.start函数订阅Producer时, 实际就是在执行这个函数
    override func start(_ generator: (Disposable) -> Signal.Observer) -> Disposable {
        let instance = makeInstance()// 1. 创建一个热信号signal
        instance.signal.observe(generator(instance.interruptHandle)) 2. 通过参数generator创建一个观察者并订阅上面创建的signal
        instance.observerDidSetup()3. 订阅signal完成 执行回调
        return instance.interruptHandle
    }

    override func makeInstance() -> Instance {
        return _make()
    }
}

现在我们知道了冷信号是如何通过Signal来保存订阅者传入的Observer对象, 下来看看这些Observer是如何被执行的:

//SignalProducer.swift
public init(_ startHandler: @escaping (Signal<Value, Error>.Observer, Lifetime) -> Void) {
        self.init(SignalCore { //通过SignalCore.init(_ action:)创建Core Core.action就是makeInstance() 然后通过Core创建Producer
            ****SignalCore.makeInstance begin****

            let disposable = CompositeDisposable()
//1. 创建一个Signal 这个Signal用于保存订阅者添加的Observer对象
            let (signal, innerObserver) = Signal<Value, Error>.pipe(disposable: disposable)
//2.1 创建一个observerDidSetup 当订阅Signal完成后就直接执行我们创建SignalProducer时传入的startHandler
//2.2 innerObserver通过startHandler传递给外部使用 外部使用innerObserver发送事件
            let observerDidSetup = { startHandler(innerObserver, Lifetime(disposable)) }
            let interruptHandle = AnyDisposable(observer.sendInterrupted)
 
//3. 通过Instance持有上面创建的Signal和observerDidSetup
            return SignalProducerCore.Instance(signal: signal,
                                               observerDidSetup: observerDidSetup,
                                               interruptHandle: interruptHandle)
            ****SignalCore.makeInstance end****
        })
    }
}

//ViewModel.swift
let producer = Producer<Int> {[unowned self] (innerObserver, _) in
    self.fetchData(completionHandler: { (data, error) in
        innerObserver.send(value: data)//外界通过startHandler使用innerObserver发送事件
        innerObserver.sendCompleted()
    })
}

简单描述一下整个流程:

  1. 创建Producer时需要传入一个startHandler:(Signal.Observer, Lifetime) -> Void闭包, 我们通过这个startHandler的Observer参数发送事件, startHandler会在Producer.core._make执行时被回调.
  2. 那么Producer.core._make闭包什么时候会执行呢? 答案是一旦有人调用Producer.start(outerObserver)函数时. _make会创建一个Signal并订阅outerObserver到Signal中, 然后将InnerObserver传入startHandler闭包并执行.

记住, Producer.start调用几次, Producer.core._make就会执行几次(也就是startHandler会执行几次).
照例, 给到一张图:


Producer.png

这张图不能用于OC或RXSwift, 这两者的冷信号并不依托热信号, 只是思路类似, 不过实现方式会略复杂些.

Property/MutableProperty

ReactiveSwift发送事件的第三种途径是Property/MutableProperty. 从冷热信号的定义上来看, Property的行为应该属于热信号, 但和上文的Signal不同, Property/MutableProperty只提供一种状态的事件: Value.(虽然它有Completed状态)
我们就暂且认为Property/MutableProperty代表那些不知道何时结束的现场直播吧.
照例, 先上一段标准代码:

let constant = Property(value: 1)
// constant.value = 2  //error: Property(value)创建的value不可变
print("initial value is: \(constant.value)")

constant.producer.startWithValues { (value) in
    print("producer received: \(value)")
}
constant.signal.observeValues { (value) in
    print("signal received: \(value)")
}

输出: initial value is: 1
     producer received: 1
let mutableProperty = MutableProperty(1)
print("initial value is: \(mutableProperty.value)")

mutableProperty.producer.startWithValues { /** 冷信号可以收到初始值value=1和2,3 */
    print("producer received \($0)")
}

mutableProperty.signal.observeValues { /** 热信号只能收到后续的变化值value=2,3 */
    print("signal received \($0)")
}
mutableProperty.value = 2 /** 设置value值就是在发送Value事件 */
mutableProperty.value = 3 /** 设置value值就是在发送Value事件 */

输出: initial value is: 1

      producer received: 1

      producer received: 2
      signal received: 2

      producer received: 3
      signal received: 3

这段代码只是演示一下Property的基本信息. 大家只需要知道Property.value不可设置, MutableProperty.value可设置. Property/MutableProperty内部有一个Producer一个Signal, 设置value即是在向这两个信号发送Value事件即可.

下面以手机号输入限制举例给到一段实际使用的示例代码:

需求: 1.用户输入手机号 限制手机号最多输入11个数字
     2.验证手机号是否有效 手机号无效需要展示错误信息 

let errorLabel: UILabel 
let sendButton: UIButton
let phoneNumerTextField: UITextField

var errorText = MutableProperty("")
var validPhoneNumer = MutableProperty("")

errorLabel.reactive.text <~ errorText //绑定错误信息到errorLabel 
sendButton.reactive.isEnabled <~ errorText.map{ $0.count == 0 } //只是演示一下什么都可以绑
sendButton.reactive.backgroundColor <~ errorText.map{ $0.count == 0 ? UIColor.red : UIColor.gray } //只是演示一下什么都可以绑
phoneNumerTextField.reactive.text <~ validPhoneNumer //绑定有效输入到输入框

//有效输入的数据源来自原始的输入框 我们对原始输入进行一些格式化
validPhoneNumer <~ phoneNumerTextField.reactive.continuousTextValues
    .map({(text) -> String in
        
    let phoneNumer = (text ?? "").substring(to: 11) //1. 最多输入11个数字, 多余部分截掉 
    let isValidPhoneNum = NSPredicate(format: "SELF MATCHES %@", "正则表达式...").evaluate(with: phoneNumer) //2. 检查手机格式是否正确
    errorText.value = isValidPhoneNum ? "手机号格式不正确" : "" //2. 格式不正确显示错误信息
    return phoneNumer //3. 返回截取后的有效输入
})

上面的代码中出现了一个新东西: <~操作符. <~非常有用, 而且实现也非常的简单, 我会在下文解释, 这里大家只需要知道: <~的左边是绑定目标(BindingTargetProvider), 右边则是数据源(BindingSource), <~会把右边数据源的发送出的Value直接绑定到左边的目标上.

目前左侧现成的绑定目标有Property/MutableProperty和一系列形如UIKit.reactive.xxx的拓展, 右侧现成的数据源则有Signal, SignalProducer以及Property/MutableProperty.
对于非现成的绑定目标, 我们可以参照现有拓展自行拓展, 非常简单, 比如给YYLabel加个拓展:

//UILabel的默认拓展
extension Reactive where Base: UILabel {
    public var text: BindingTarget<String?> {
        return makeBindingTarget { $0.text = $1 }//$0表示UI控件本身 $1表示value
    }

    public var attributedText: BindingTarget<NSAttributedString?> {
        return makeBindingTarget { $0.attributedText = $1 }
    }
}

//照猫画虎给YYLabel也加上拓展
extension Reactive where Base: YYLabel {
    public var text: BindingTarget<String?> {
        return makeBindingTarget { $0.text = $1 }//$0表示UI控件本身 $1表示value
    }
    
    public var attributedText: BindingTarget<NSAttributedString?> {
        return makeBindingTarget { $0.attributedText = $1 }
    }
}
  • <~的实现概述

Property的实现比较简单, 大体流程就是内部存了一个Signal然后只转发Signal的Value事件, 我就不多说了, 主要说说<~的实现(其实也非常简单...).

//数据源
public protocol BindingSource: SignalProducerConvertible {
    associatedtype Value
    associatedtype Error: Swift.Error

    var producer: SignalProducer<Value, Error> { get }
}
extension Signal: BindingSource {}
extension SignalProducer: BindingSource {}


//绑定目标提供者
public protocol BindingTargetProvider {
    associatedtype Value
    var bindingTarget: BindingTarget<Value> { get }
}
//绑定目标
public struct BindingTarget<Value>: BindingTargetProvider {
    public let lifetime: Lifetime //这里定义何时取消订阅数据源
    public let action: (Value) -> Void //这里定义了如何利用数据源发送的Value(一般来说就是简单的用Value设置某个属性)
    ...其他代码 略
}

extension BindingTargetProvider {
    @discardableResult
    public static func <~ <Source: BindingSource>
        (provider: Self, source: Source) -> Disposable?
        where Source.Value == Value, Source.Error == NoError {
        //订阅右边的数据源提供的producer, 在订阅回调中执行绑定目标的action闭包
        return source.producer
            .take(during: provider.bindingTarget.lifetime)
            .startWithValues(provider.bindingTarget.action)
    }
}

可以看到<~的实现非常简单, 只有三行代码, 订阅右边的数据源提供的producer, 在订阅回调中执行绑定目标的action闭包.
然后我们看看ReactiveSwift是如何给UI控件添加BindingTarget的, 以Label举例:

public protocol ReactiveExtensionsProvider: class {}
extension ReactiveExtensionsProvider {
    public var reactive: Reactive<Self> {
        return Reactive(self)
    }
    public static var reactive: Reactive<Self>.Type {
        return Reactive<Self>.self
    }
}//任何对象获取 someObject.reactive时就返回一个Reactive结构体, Reactive.base即是someObject

public struct Reactive<Base> {
    public let base: Base
    fileprivate init(_ base: Base) {
        self.base = base
    }
}
extension Reactive where Base: NSObjectProtocol {
    //创建一个BindingTarget BindingTarget.action则是在UI线程操作value
    public func makeBindingTarget<U>(on scheduler: Scheduler = UIScheduler(), _ action: @escaping (Base, U) -> Void) -> BindingTarget<U> {
        return BindingTarget(on: scheduler, lifetime: lifetime) { [weak base = self.base] value in
            if let base = base {
                action(base, value)
            }
        }
    }
}

extension Reactive where Base: UILabel {
    //当获取label.reactive.text时 就创建一个makeBindingTarget makeBindingTarget的action则是直接设置self.text = value
    public var text: BindingTarget<String?> {
        return makeBindingTarget { $0.text = $1 }//$0即是label自己 $1即是value
    }
}

不到30行, 就不给图了...

Action/CocoaAction

Action是最后一种发送事件的途径, 不过和其他途径不同, 它并不直接发送事件, 而是生产信号, 由生产的信号来发送事件. 最重要的是, Action是唯一一种可以接受订阅者输入的途径. 举个栗子:
上文中的现场直播也好, 视频文件也好, 其实都是家长(ViewModel)提供的, 一般家长的特色就是只是给予, 但并不在乎你喜不喜欢, 而且你还没办法通过信号跟他进行沟通. 而Action不同, 它提供让你跟家长沟通的接口apply(input), 当然, 虽然你说了你想说的(传递了数据), 但家长(ViewModel)采不采纳就是另一回事了.
这里直接上Action的实际使用代码:

//Action.swift
public final class Action<Input, Output, Error: Swift.Error>

public convenience init(execute: @escaping (Input) -> SignalProducer<Output, Error>)
typealias APIAction<O> = ReactiveSwift.Action<[String: String]?, O, APIError>

1. 创建一个Action 输入类型为[String: String]? 输出类型为Int 错误类型为APIError
let action = APIAction<Int> { (input) -> APIProducer<Int> in
    print("input: ", input)
    return APIProducer({ (innerObserver, _) in
        
        //发起网络请求
        innerObserver.send(value: 1)
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2, execute: {
            innerObserver.send(value: 2)
            innerObserver.sendCompleted()
        })
    })
}

2. 订阅Action的执行事件
action.events.observe { print("did received Event: \($0)") }

3. 订阅Action的各种输出事件
action.values.observeValues { print("did received Value: \($0)")  }
//action.errors.observeValues { print("did received Error: \($0)")  }
//action.completed.observeValues { print("did received completed: \($0)") }

4. 执行Action 开始输出
action.apply(["1": "xxx"]).start()

5. 在返回的Producer还未结束前继续执行Action 什么也不会输出
for i in 0...10 {
    action.apply([String(i): "xxx"]).start()
}
输出: input:  Optional(["0": "xxx"])
      did received Value: 1
      did received Event: VALUE VALUE 1
           ....两秒后....
      did received Value: 2
      did received Event: VALUE VALUE 2
      did received Event: VALUE COMPLETED
      did received Event: COMPLETED

Action的三个泛型从左到右依次定义了输入类型, 输出类型, 错误类型, 通常我都是直接typealias的, 不然写起来真是太长了. 上面的代码主要描述以下信息:

  1. 通过闭包execute: (Input) -> SignalProducer 创建一个Action, 我们可以通过execute.input获取来自订阅者的输入, 然后我们需要返回一个SignalProducer, SignalProducer封装了事件的获取/发送逻辑.
  2. 通过Action.events可以订阅Action本身的事件, 此时的Event.Value还是一个Event.
  3. 通过Action.values/errors/completed可以订阅初始化闭包中返回的SignalProducer的各种事件, 我们可以订阅这些事件对返回的结果做相应的处理.
  4. 通过action.apply(input).start()提供输入信息并执行Action.
  5. 在返回的Producer还未结束前执行Action是没用的, 只有上一个返回的Producer无效后, Action才能再次执行. (这个特性用来处理按钮的多次点击发送网络请求非常有用.)

我们看到第5条Action通过返回的Producer的状态来控制自身的可执行与否, 大多数时候这就足够了, 但保不齐你需要更精准的状态控制, 此时你需要的是下面的函数:

public convenience init<P: PropertyProtocol>(enabledIf isEnabled: P, execute: @escaping (Input) -> SignalProducer<Output, Error>) where P.Value == Bool

这个函数通过外部传入的Property<Bool>来控制Action的可执行与否, 我们可以用它来做较为精准的状态控制. 举个栗子:

需求: 只在输入框有输入时才可以点击按钮发起网络请求

let executeButton: UIButton

//创建一个MutableProperty<Bool>的状态控制信号 控制逻辑随便写
let enabled = MutableProperty(false)
//有输入才能发起请求
enabled <~ phoneNumberTF.reactive.continuousTextValues.map { return ($0 ?? "").count > 0 }

//传入状态控制信号即可
let action = APIAction<Int>(enabledIf: enabled) { (input) -> APIProducer<Int> in
    print("input: ", input)
    return APIProducer({ (innerObserver, _) in
        
        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5, execute: {
            innerObserver.send(value: 1)
            innerObserver.sendCompleted()
        })
    })
}

action.values.observeValues { print("did received value: \($0)") }

executeButton.reactive.controlEvents(.touchUpInside).observeValues { (sender) in
    action.apply(["xxx": "xxx"]).start()
}

例子很简单, 不多解释, 最后介绍一下Action的Cocoa拓展: CocoaAction.

ReactiveCocoa为一些可点击的UI控件(如UIButton, UIBarButtonItem...)都添加了一个reactive.pressed属性, 我们可以通过设置reactive.pressed很方便的添加点击操作, 不过这个属性并不属于Action而是CocoaAction. 下面简单给到一段CocoaAction的用法:

typealias TextAction<O> = ReactiveSwift.Action<String?, O, APIError>

let executeButton: UIButton
let enabled = MutableProperty(false)
enabled <~ phoneNumberTF.reactive.continuousTextValues.map { return ($0 ?? "").count > 0 }

let action = TextAction<Int>(enabledIf: enabled) { (input) -> APIProducer<Int> 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<UIButton>(action, input: self.phoneNumberTF.text) //这样写 action得到的永远都是""
//let cocoaAction1 = CocoaAction<UIButton>(action, input: nil)

//初始化CocoaAction法2 input为一个非固定值
let cocoaAction2 = CocoaAction<UIButton>(action) { _ in
    let input = "xxx" //各种操作得到一个输入
    return input
}

我们看到, 初始化CocoaAction需要两个参数, action和input, action定义了点击控件时执行的操作, 而input则定义了操作执行时输入的数据, input的类型需要合action.input的类型一一对应.
这里只需要注意一点: 如果action的输入是一个变化值, 比如来自某个输入框textField, 那么你应该通过闭包来提供这个输入而不是直接传入textField.text.

  • Action的源码读起来好累, 不写...

ReactiveSwift的基本知识和用法, 到这里就介绍完了. 最后我们把上面提到的东西全部串起来, 给到一个Hello ReactiveSwift.

Hello ReactiveSwift

我们的需求是一个很普通的注册页面:


image.png

鉴于这只是一个简单的Demo, 我不会严格按照自己的开发模式来写, 给到一个懒人版的MVVM即可, 首先是View的部分:

@IBOutlet weak var accountTF: UITextField! //账号输入框
@IBOutlet weak var passwordTF: UITextField! //密码输入框
@IBOutlet weak var ensurePasswordTF: UITextField! //确认密码输入框

@IBOutlet weak var verifyCodeTF: UITextField! //验证码输入框
@IBOutlet weak var verifyCodeButton: UIButton! //获取验证码按钮

@IBOutlet weak var errorLabel: UILabel! //错误描述Label
@IBOutlet weak var submitButton: UIButton! //提交注册按钮

这部分大家简单瞄一眼即可, 记得大概属性名即可, 接着我们定义一下ViewModel的接口部分, Protocol或Public皆可, 这里我选择前者:

protocol RegisterViewModelProtocol {
    //设置数据源依赖 (数据依赖是在初始化时注入还是使用前注入不是本文讨论的话题 懒人版只选择最方便的)
    func setInput(accountInput: NSignal<String?>,
                  passwordInput: NSignal<String?>,
                  ensurePasswordInput: NSignal<String?>,
                  verifyCodeInput: NSignal<String?>)
    
    var validAccount: MutableProperty<String> { get } //格式化好的账号输出
    var validPassword: MutableProperty<String> { get } //格式化好的密码输出
    var validEnsurePassword: MutableProperty<String> { get } //类似
    var validVerifyCode: MutableProperty<String> { get } //类似
    var errorText: MutableProperty<String> { get } //错误信息输出

    var verifyCodeText: MutableProperty<String> { get } //验证码文案输出
    var getVerifyCodeAction: AnyAPIAction{ get } // 验证码按钮点击事件
    
    var submitAction: AnyAPIAction { get } //提交按钮点击事件
}

我们先来处理输入格式的部分:

private let InvalidAccount = "手机号格式不正确"
private let InvalidPassword = "密码格式不正确"
private let InvalidVerifyCode = "验证码格式不正确"

extension RegisterViewModel: RegisterViewModelProtocol{}
class RegisterViewModel {
    
    private(set) var validAccount = MutableProperty("")
    private(set) var validPassword = MutableProperty("")
    private(set) var validEnsurePassword = MutableProperty("")
    private(set) var validVerifyCode = MutableProperty("")
    
    private var errors = (account: InvalidAccount, password: InvalidPassword, verifyCode: InvalidVerifyCode)
    
    func setInput(accountInput: NSignal<String?>, passwordInput: NSignal<String?>, ensurePasswordInput: NSignal<String?>, verifyCodeInput: NSignal<String?>) {
        
        //账号: 11位手机号 最多输入11个数字
        validAccount <~ accountInput.map({[unowned self] (text) -> String in
            
            let account = (text ?? "").substring(to: 11)
            self.errors.account = !account.isValidPhoneNum ? InvalidAccount : ""
            return account
        })
        
        //密码: 6~16位数字和字符的组合 最多输入16个字符
        validPassword <~ passwordInput.map({[unowned self] (text) -> String in
            
            let password = (text ?? "").substring(to: 16)
            let isValidPassword = NSPredicate(format: "SELF MATCHES %@", "^(?=.*[a-zA-Z0-9].*)(?=.*[a-zA-Z\\W].*)(?=.*[0-9\\W].*).{6,16}$")
            self.errors.password = !isValidPassword.evaluate(with: password) ? InvalidPassword : ""
            return password
        })
        
        //确认密码: 最多输入16个字符 我们会在下文验证两次输入是否一致
        validEnsurePassword <~ ensurePasswordInput.map({
            return ($0 ?? "").substring(to: 16)
        })
        
        //验证码: 1~6位数字或字符 最多输入6个字符
        validVerifyCode <~ verifyCodeInput.map({[unowned self] (text) -> String in
            
            let verifyCode = (text ?? "").substring(to: 6)
            let isValidVerifyCode = NSPredicate(format: "SELF MATCHES %@", "\\w+")
            self.errors.verifyCode = !isValidVerifyCode.evaluate(with: verifyCode) ? InvalidVerifyCode : ""
            return verifyCode
        })
    }
}

有了有效输入后, 我们就可以着手点击事件处理了, 先处理验证码的点击事件:

//因为没后台 所以给到一个任意输出的信号 只要不输出Error 那就是请求成功
typealias AnyAPIAction = ReactiveSwift.Action<Any?, Any?, APIError>
typealias AnyAPIProducer = ReactiveSwift.SignalProducer<Any?, APIError>

1. 首先我们需要一个网络请求
class UserAPIManager: HTTPAPIManager {

    //获取验证码 (假装有后台)
    func getVerifyCode(phoneNumber: String) -> AnyAPIProducer {
        return arc4random() % 2 == 1 ? AnyAPIProducer(error: APIError(489489)) : AnyAPIProducer(value: true)
    }
}
class RegisterViewModel {

    private var timer: Timer?
    private var time = MutableProperty(60)

    private(set) var errorText = MutableProperty("")
    private(set) var verifyCodeText = MutableProperty("验证码")
    
    2. 然后需要一个Action发起网络请求
    private(set) lazy var getVerifyCodeAction = AnyAPIAction(enabledIf: **self.enableGetVerifyCode) { [unowned self] _ -> AnyAPIProducer in
        return self.getVerifyCodeProducer
    }
    
    3. Action的执行条件是: 1)手机号格式正确 2)验证码倒计时已结束或并未开始
    private var enableGetVerifyCode: Property<Bool> {
        return Property.combineLatest(time, errorText).map({ (time, error) -> Bool in
            return error != InvalidAccount && (time <= 0 || time >= 60)
        })
    }
    
    4. 发起验证码网络请求 请求成功后启动timer进行60s倒计时
    private var getVerifyCodeProducer: AnyAPIProducer {
        return UserAPIManager().getVerifyCode(phoneNumber: self.validAccount.value).on(value: { [unowned self] (value) in
            
            self.timer?.invalidate()
            self.time.value = 60;
            self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timeDown), userInfo: nil, repeats: true)
        })
    }
    
    //5. 倒计时 设置验证码按钮文案
    @objc private func timeDown() {
        
        if (self.time.value > 0) {
            self.verifyCodeText.value = String(self.time.value) + "s"
        } else {
            
            timer?.invalidate()
            verifyCodeText.value = "验证码";
        }
        self.time.value -= 1;
    }
}

接着是提交注册的点击事件:

1. 首先我们需要一个网络请求
class UserAPIManager: HTTPAPIManager {

    func registerProducer(account: String, password: String, verifyCode: String) -> AnyAPIProducer {
        //注册用户 (假装有后台)
        return arc4random() % 2 == 1 ? AnyAPIProducer(error: APIError(789465)) : AnyAPIProducer(value: true)
    }
}
class RegisterViewModel {
    private(set) var errorText = MutableProperty("")

    2. 然后需要一个Action发起网络请求
    private(set) lazy var submitAction: AnyAPIAction = AnyAPIAction(enabledIf:  self.enableSubmit) { [unowned self] _ -> AnyAPIProducer in
        return self.submitProducer
    }

    3. Action的执行条件是: 1)手机号格式正确 2)密码格式正确 3)两次输入密码一致 4)验证码格式正确
    private var enableSubmit: Property<Bool> {
        return Property.combineLatest(validAccount, validPassword, validEnsurePassword, validVerifyCode).map({ [unowned self] (account, password, ensurePassword, verifyCode) -> Bool in
            
            //顺便在这里设置错误信息
            if self.errors.account.count > 0 {
                self.errorText.value = self.errors.account
            } else if self.errors.password.count > 0 {
                self.errorText.value = self.errors.password
            } else if password != ensurePassword  {
                self.errorText.value = "两次输入的密码不一致"
            } else if self.errors.verifyCode.count > 0 {
                self.errorText.value = self.errors.verifyCode
            } else {
                self.errorText.value = ""
            }
            
            return self.errorText.value.count == 0
        })
    }

    4.发起提交注册网络请求 请求成功后保存一些用户信息
    private var submitProducer: AnyAPIProducer {
        return UserAPIManager().registerProducer(account: validAccount.value, password: self.validPassword.value, verifyCode: validVerifyCode.code).on(value: { [unowned self] (value) in
            
            self.timer?.invalidate()
            UserDefaults.account = value
        })
    }
}

ViewModel的接口都实现了, 接下来就是Controller来使用这些接口对View进行绑定:

class RegisterViewController: UIViewController {

    private var viewModel: RegisterViewModelProtocol! {
        didSet {
            //1. 设置依赖数据源
            viewModel.setInput(accountInput: accountTF.reactive.continuousTextValues,
                               passwordInput: passwordTF.reactive.continuousTextValues,
                               ensurePasswordInput: ensurePasswordTF.reactive.continuousTextValues,
                               verifyCodeInput: verifyCodeTF.reactive.continuousTextValues)
            
            //2. 绑定有效输入
            accountTF.reactive.text <~ viewModel.validAccount
            passwordTF.reactive.text <~ viewModel.validPassword
            ensurePasswordTF.reactive.text <~ viewModel.validEnsurePassword
            
            //3. 绑定错误信息
            errorLabel.reactive.text <~ viewModel.errorText.signal.skip(first: 1)
            
            //4. 绑定验证码相关信息
            verifyCodeTF.reactive.text <~ viewModel.validVerifyCode
            verifyCodeButton.reactive.title <~ viewModel.verifyCodeText
            //5. 绑定验证码点击事件(因为前面已经注入了验证码输入 所以这里我们不需要给到input)
            verifyCodeButton.reactive.pressed = CocoaAction(viewModel.getVerifyCodeAction)
            viewModel.getVerifyCodeAction.errors.observeValues {[unowned self] (error) in
                self.view.toast(error.reason)//验证码获取失败了 礼貌性的给个toast
            }
            
            //6. 绑定提交注册点击事件(同样的 不需要给到input)
            submitButton.reactive.pressed = CocoaAction(viewModel.submitAction)
            viewModel.submitAction.errors.observeValues {[unowned self] (error) in
                self.view.toast(error.reason)//注册失败了 礼貌性的给个toast
            }
            viewModel.submitAction.values.observeValues {[unowned self] (value) in
                
                //注册成功返回首页 至于信息保存一类的事情ViewModel已经做完了 Controller做好UI展示就够了
                self.view.toast("注册成功")
                self.navigationController?.popViewController(animated: true)
            }
        }
    }
}

事实上, 在一个函数写完所有的绑定逻辑并不是什么好的代码规范(我应该把它拆开成多个小函数), 但考虑到这部分代码不多且只是个demo, 就先凑合吧. 荆轲刺秦王...

本文附带的Swift版Demo地址
本文附带的OC版Demo地址

也许你会用得上这些

ReactiveSwift的更多API示例代码请下载ReactiveSwift. 下载下来后: 1) 打开ReactiveSwift.xcworkspace 2)切换到 Result-Mac scheme进行编译 3)然后切换到 ReactiveSwift-macOS scheme进行编译 4)跟着ReactiveSwift.playground敲吧

ReactiveSwift的官方设计文档(只有英文版)
本文Demo中涉及到的MVVM概述

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容