Swift中的RactiveCocoa (上)

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

前言

大概是這樣, Swift4.0出了, 從新梳理Swift知識, 對比了下RXSwift和ReactiveSwift, 喜歡ReactiveSwift多一些, 想了想, 出份基礎教程. 建議新人朋友只看如何使用, 至於實現概述看看最後的總結和圖瞭解一下思路就好了.git

目錄

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

Event

在ReactiveSwift中, 信息的載體(value/error)對應的是一個枚舉Event, Event的定義以下:github

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

前三個狀態顧名思義就不解釋了, 第四個狀態interrupted表示事件被打斷, 除非你去訂閱一個已經無效的信號, 不然這個狀態不會出現, 因此不用太多關注這個狀態.正則表達式

須要注意的點: 當信號發送非Value的Event時, 那麼這個信號就無效了. 無效的緣由多是failed(失敗), completed(壽終正寢), interrupted(早就無效了).swift

Observer

上面說過Event是信息的載體, 而這裏的Observer則是信息的處理邏輯封裝. Observer的主要代碼以下:api

//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的處理邏輯.bash

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來控制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概述

相關文章
相關標籤/搜索