📂
RxSwift
  • RxSwift 中文文档
  • 1. 为什么要使用 RxSwift?
  • 2. 你好 RxSwift!
  • 3. 函数响应式编程
    • 3.1 函数式编程
    • 3.2 函数式编程 -> 函数响应式编程
    • 3.3 数据绑定
  • 4. RxSwift 核心
    • 4.1 Observable - 可监听序列
      • Single
      • Completable
      • Maybe
      • Driver
      • Signal
      • ControlEvent
    • 4.2 Observer - 观察者
      • AnyObserver
      • Binder
    • 4.3 Observable & Observer 既是可监听序列也是观察者
      • AsyncSubject
      • PublishSubject
      • ReplaySubject
      • BehaviorSubject
      • Variable(已弃用)
      • ControlProperty
    • 4.4 Operator - 操作符
    • 4.5 Disposable - 可被清除的资源
    • 4.6 Schedulers - 调度器
    • 4.7 Error Handling - 错误处理
  • 5. 如何选择操作符?
    • amb
    • buffer
    • catchError
    • combineLatest
    • concat
    • concatMap
    • connect
    • create
    • debounce
    • debug
    • deferred
    • delay
    • delaySubscription
    • dematerialize
    • distinctUntilChanged
    • do
    • elementAt
    • empty
    • error
    • filter
    • flatMap
    • flatMapLatest
    • from
    • groupBy
    • ignoreElements
    • interval
    • just
    • map
    • merge
    • materialize
    • never
    • observeOn
    • publish
    • reduce
    • refCount
    • repeatElement
    • replay
    • retry
    • sample
    • scan
    • shareReplay
    • single
    • skip
    • skipUntil
    • skipWhile
    • startWith
    • subscribeOn
    • take
    • takeLast
    • takeUntil
    • takeWhile
    • timeout
    • timer
    • using
    • window
    • withLatestFrom
    • zip
  • 6. 更多示例
    • ImagePicker - 图片选择器
    • TableViewSectionedViewController - 多层级的列表页
    • Calculator - 计算器
  • 7. RxSwift 常用架构
    • 7.1 MVVM
      • Github Signup(示例)
    • 7.2 RxFeedback
      • Github Search(示例)
    • 7.3 ReactorKit
      • Github Search(示例)
  • 8. RxSwift 生态系统
  • 9. 学习资源
  • 10. 关于本文档
    • 10.1 文档更新日志
  • 食谱
    • RxSwift 5 更新了什么?
    • RxRelay
    • 纯函数
    • 附加作用
    • 共享附加作用
Powered by GitBook
On this page
  • 什么是纯函数?
  • 纯函数用于状态管理
  • 登录状态管理
  • 下一步 -- 引入 Store
  • 总结
  • 参考

Was this helpful?

  1. 食谱

纯函数

PreviousRxRelayNext附加作用

Last updated 4 years ago

Was this helpful?

什么是纯函数?

在函数式编程里我们会经常谈到这两个概念。一个是 。另一个是 (副作用)。这里我们就结合实际来介绍一下 和 。

下面我们给出两个函数 increaseA 和 increaseB,他们其中一个是 ,另一个不是 :

var state = 0

func increaseA() {
    state += 1
}

increaseA()

print(state) // 结果: 1
func increaseB(state: Int) -> Int {
    return state + 1
}

let state = increaseB(state: 0)

print(state) // 结果: 1

...

...

...

... 经过 10 秒后

因为他特别 纯洁:除了用入参 state 计算返回值以外没做任何其他的事情。

我们再来做以下两个测试,然后猜测他们能不能测试成功:

func testIncreaseA() {
    increaseA()
    state == 1 // 结果:?? 🤔
}
func testIncreaseB() {
    let state = increaseB(state: 0)
    state == 1 // 结果:true 😎
}

...

...

...

... 经过 20 秒后

嗯... 这里我们可以肯定第二个测试 testIncreaseB 会成功。0 + 1 肯定等于 1。那第一个测试呢?这可不好说了,我们并不知道 increaseA 是在什么环境下被调用的,不知道在这个环境下初始 state 是多少。如果他是 0 那测试就会成功的,如果他不是 0 那测试就会失败的。因此在不知道所处环境时,我们无法判断测试是否会成功。

  • 发起网络请求

  • 刷新 UI

  • 读写数据库

  • 获取位置信息

  • 使用蓝牙模块

  • 打印输出

  • ...

纯函数用于状态管理

typealias State = Int

enum Event {
    case increase
    case decrease
}

func reduce(_ state: State, event: Event) -> State {
    switch event {
    case .increase:
        return state + 1
    case .decrease:
        return state - 1
    }
}

首先,我们得有个状态:

typealias State = Int

然后,我们要有各种事件:

enum Event {
    case increase
    case decrease
}
func reduce(_ state: State, event: Event) -> State {
    switch event {
    case .increase:
        return state + 1
    case .decrease:
        return state - 1
    }
}

这样,我们就可以做测试了,当 App 处于某个状态时,发生了某个事件,会产生一个结果,这个结果是否符合我们的预期:

func testReduce() {

    let state1 = reduce(0, event: .increase)
    state1 == 1 // 结果:true 😎

    let state2 = reduce(10, event: .decrease)
    state2 == 9 // 结果:true 😎
}

我们再来看下 reduce 方法:

func reduce(_ state: State, event: Event) -> State { ... }

我们有没有获得一点点灵感...

...

...

...

...

...

... 经过 60 秒后

希望你已经获得答案了。

当程序处于某个特定状态时,发生了某个特定事件,会产生某个唯一的结果。这个结果与所处的环境无关,不论是处于应用程序运行环境,还是在测试环境。这个结果只和初始状态以及发生的事件有关。因此,程序的行为是可以被预测的,而且程序运行时的状态更新,可以在测试环境中被模拟出来。

...

...

...

...

...

... 经过 60 秒后

现在,我们来看一个相对复杂的例子:

登录状态管理

typealias UserID = String

enum LoginError: Error, Equatable {
    case usernamePasswordMismatch
    case offline
}

struct State: Equatable {

    var username: String
    var password: String

    var loading: Bool
    var data: UserID?
    var error: LoginError?

    enum Event {
        case onUpateUsername(String)
        case onUpatePassword(String)
        case onTriggerLogin
        case onLoginSuccess(UserID)
        case onLoginError(LoginError)
    }

    static func reduce(_ state: State, event: Event) -> State {
        var newState = state
        switch event {
        case .onUpateUsername(let username):
            newState.username = username
        case .onUpatePassword(let password):
            newState.password = password
        case .onTriggerLogin:
            newState.loading = true
            newState.data = nil
            newState.error = nil
        case .onLoginSuccess(let userId):
            newState.loading = false
            newState.data = userId
        case .onLoginError(let error):
            newState.loading = false
            newState.error = error
        }
        return newState
    }
}

首先,我们得有个状态:

struct State: Equatable {

    var username: String    // 输入的用户名
    var password: String    // 输入的密码

    var loading: Bool       // 登录中
    var data: UserID?       // 登录成功
    var error: LoginError?  // 登录失败

    ...
}

然后,我们要有各种事件:

    enum Event {
        case onUpateUsername(String)    //  更新用户名
        case onUpatePassword(String)    //  更新密码
        case onTriggerLogin             //  触发登录
        case onLoginSuccess(UserID)     //  登录成功
        case onLoginError(LoginError)   //  登录失败
    }
    static func reduce(_ state: State, event: Event) -> State {
        var newState = state
        switch event {
        case .onUpateUsername(let username):
            newState.username = username
        case .onUpatePassword(let password):
            newState.password = password
        case .onTriggerLogin:
            newState.loading = true
            newState.data = nil
            newState.error = nil
        case .onLoginSuccess(let userId):
            newState.loading = false
            newState.data = userId
        case .onLoginError(let error):
            newState.loading = false
            newState.error = error
        }
        return newState
    }

现在我们可以在测试环境模拟各种事件,并且判断结果是否符合预期:

  • 更新用户名事件

func testOnUpateUsername() {

    let state = State(
        username: "",
        password: "",
        loading: false,
        data: nil,
        error: nil
    )

    let newState = State.reduce(state, event: .onUpateUsername("beeth0ven"))

    let expect = State(
        username: "beeth0ven",
        password: "",
        loading: false,
        data: nil,
        error: nil
    )

    newState == expect // 结果:true 😎
}
  • 更新密码事件

func testOnUpatePassword() {

    let state = State(
        username: "beeth0ven",
        password: "",
        loading: false,
        data: nil,
        error: nil
    )

    let newState = State.reduce(state, event: .onUpatePassword("123456"))

    let expect = State(
        username: "beeth0ven",
        password: "123456",
        loading: false,
        data: nil,
        error: nil
    )

    newState == expect // 结果:true 😎
}
  • 触发登录事件

func testOnTriggerLogin() {

    let state = State(
        username: "beeth0ven",
        password: "123456",
        loading: false,
        data: nil,
        error: nil
    )

    let newState = State.reduce(state, event: .onTriggerLogin)

    let expect = State(
        username: "beeth0ven",
        password: "123456",
        loading: true,
        data: nil,
        error: nil
    )

    newState == expect // 结果:true 😎
}
  • 登录成功事件

func testOnLoginSuccess() {

    let state = State(
        username: "beeth0ven",
        password: "123456",
        loading: true,
        data: nil,
        error: nil
    )

    let newState = State.reduce(state, event: .onLoginSuccess("userID007"))

    let expect = State(
        username: "beeth0ven",
        password: "123456",
        loading: false,
        data: "userID007",
        error: nil
    )

    newState == expect // 结果:true 😎
}
  • 登录失败事件

func testOnLoginError() {

    let state = State(
        username: "beeth0ven",
        password: "123456",
        loading: true,
        data: nil,
        error: nil
    )

    let newState = State.reduce(state, event: .onLoginError(.usernamePasswordMismatch))

    let expect = State(
        username: "beeth0ven",
        password: "123456",
        loading: false,
        data: nil,
        error: .usernamePasswordMismatch
    )

    newState == expect // 结果:true 😎
}

这样我们可以轻易掌控程序的运行状态,以及各种状态更新。

下一步 -- 引入 Store

class Store {

    // 观察者,用于响应状态更新,第一个 State? 为旧状态,第二个 State 为当前状态
    typealias Observer = (State?, State) -> Void

    private(set) var state: State  // 当前状态
    private var _observers: [UUID: Observer]  // 所有的观察者

    // 初始化
    init(initailState: State) {
        self.state = initailState
        self._observers = [:]
    }

    // 发出事件
    func dispatch(event: State.Event) {
        let oldState = self.state
        self.state = State.reduce(self.state, event: event)
        _publish(oldState: oldState, newState: self.state)
    }

    // 订阅状态更新
    func subscribe(observer: @escaping Observer) -> UUID {
        let subscriptionID = UUID() //  UUID 是唯一标识符,该 id 可用于取消订阅
        _observers[subscriptionID] = observer
        observer(nil, self.state) // 订阅时,将当前状态回放给该观察者
        return subscriptionID
    }

    // 取消订阅
    func unsubscribe(_ subscriptionID: UUID) {
        _observers.removeValue(forKey: subscriptionID)
    }

    // 私有方法,通知所有的观察者,状态已经更新了
    private func _publish(oldState: State?, newState: State) {
        _observers.values.forEach { observer in
            observer(oldState, newState)
        }
    }
}

如何使用 Store:

func useStore() {

    let initailState = State(
        username: "",
        password: "",
        loading: false,
        data: nil,
        error: nil
    )

    let store = Store(initailState: initailState)

    // 以下变量 newStates 和 oldStates 用于录制状态历史
    var newStates: [State] = []
    var oldStates: [State?] = []

    let subscriptionID = store.subscribe { (oldState, newState) in
        newStates.append(newState)
        oldStates.append(oldState)
    }

    // 模拟真实事件
    store.dispatch(event: .onUpateUsername("beeth0ven"))
    store.dispatch(event: .onUpatePassword("123456"))

    // 取消订阅
    store.unsubscribe(subscriptionID)

    // 描叙预期 
    let expectNewStates = [
        State(
            username: "",
            password: "",
            loading: false,
            data: nil,
            error: nil
        ),
        State(
            username: "beeth0ven",
            password: "",
            loading: false,
            data: nil,
            error: nil
        ),
        State(
            username: "beeth0ven",
            password: "123456",
            loading: false,
            data: nil,
            error: nil
        )
    ]

    let expectOldStates = [
        nil,
        State(
            username: "",
            password: "",
            loading: false,
            data: nil,
            error: nil
        ),
        State(
            username: "beeth0ven",
            password: "",
            loading: false,
            data: nil,
            error: nil
        )
    ]

    // 比对结果
    newStates == expectNewStates // 结果:true 😎
    oldStates == expectOldStates // 结果:true 😎
}

以上是在单元测试环境下,

首先下创建 Store:

    let initailState = State(
        username: "",
        password: "",
        loading: false,
        data: nil,
        error: nil
    )

    let store = Store(initailState: initailState)

然后,订阅程序状态,并且将这些状态录制下来:

    var newStates: [State] = []
    var oldStates: [State?] = []

    let subscriptionID = store.subscribe { (oldState, newState) in
        newStates.append(newState)
        oldStates.append(oldState)
    }

然后,模拟输入用户名事件和输入密码事件:

    store.dispatch(event: .onUpateUsername("beeth0ven"))
    store.dispatch(event: .onUpatePassword("123456"))

然后,取消订阅:

    store.unsubscribe(subscriptionID)

最后,比对录制的状态是否符合预期:

    let expectNewStates = [
        State(
            username: "",
            password: "",
            loading: false,
            data: nil,
            error: nil
        ),
        State(
            username: "beeth0ven",
            password: "",
            loading: false,
            data: nil,
            error: nil
        ),
        State(
            username: "beeth0ven",
            password: "123456",
            loading: false,
            data: nil,
            error: nil
        )
    ]

    let expectOldStates = [
        nil,
        State(
            username: "",
            password: "",
            loading: false,
            data: nil,
            error: nil
        ),
        State(
            username: "beeth0ven",
            password: "",
            loading: false,
            data: nil,
            error: nil
        )
    ]

    newStates == expectNewStates // 结果:true 😎
    oldStates == expectOldStates // 结果:true 😎

这就是如何在测试环境里面使用 Store,那么在 App 里面如何使用 Store 呢。 一个 相对简单(并未优化) 的方法,就是将 Store 注入到对应的组件里面,这里以 ViewController 为例:

  • ViewController 可以使用 store.subscribe 方法订阅程序的状态。当状态更新时,比对新旧状态,然后刷新过时了的 UI。

  • 当用户触发某个事件时,调用 store.dispatch 方法将事件发出去,如:当用户点击登录按钮时,就调用 store.dispatch(event: .onTriggerLogin)。

  • 在 ViewController 的 deinit 方法里面注销订阅 store.unsubscribe(subsriptionID)。

总结

参考

他们的作用差不多,使 state + 1, 我们可以猜测一下 increaseA 和 increaseB 哪一个是 ?

现在公布答案:increaseB 是 ,increaseA 不是

为什么 increaseB 是 ?

那为什么 increaseA 不是 ?

因为他修改了函数本体以外的值 state, 他拥有这个 ,因此他 并不纯洁 就不是 。

由于 increaseA 存在修改外部 state 的 所以他不是 。事实上如果函数有以下任意一种作用,他也不是纯函数:

我们将这些作用称为函数的 (副作用)。

而 的定义就是: 没有 的函数,并且在参数相同时,返回值也一定相同。

因此在已知执行逻辑时, 所产生的结果是可以被预测的。一些现代化的库都利用了这个特性来做状态管理,如:, , 等等。

我们用一个足够简单的例子来演示,如何用 做状态管理:

这个例子似乎过于简单,以至于我们看不出他有什么特别的。好吧,我承认他的主要目的是向大家演示,用 做状态管理的基本单元是什么。

最后,我们要有一个 来管理我们的状态:

以上两个测试都是成功的。当然这里的状态管理过于简单。而真实应用程序的状态都是非常复杂的。并且程序的行为都是很难预测的。要解决这个问题,我们要感谢 ,还记得他的特征吗?

在参数相同时,返回值也一定相同。

我们重新走下流程 😄,用 做状态管理:

最后,我们要有一个 来管理我们的状态:

现在,我们知道如何用 做状态管理了。不过当前的代码形态,离投入生产环境,还存在好几个过度形态。这些过度形态有的是围绕如何引入 ,而做了一些应用架构。在这个问题上,不同地架构也提出了不同的解决方案,如: 用 feedbackLoop 引入 , 用 middleware 引入 等等。这里就不一一介绍了,这些库的官方网站都会有相关说明。

最后,我们还是将代码演化到下一个形态,这里我选择使用 流派。因为个人的觉得他的知识依赖要少一些,可以让更多读者从中获益。

本节主要介绍了 和 ,期间还演示如何用 做状态管理的。最后还演化出了一个极简版的 。希望大家可以从中获益!

纯函数
附加作用
纯函数
附加作用
纯函数
纯函数
纯函数
纯函数
纯函数
纯函数
纯函数
附加作用
纯函数
附加作用
纯函数
附加作用
纯函数
附加作用
纯函数
RxFeedback
Redux
ReactorKit
纯函数
纯函数
纯函数
纯函数
纯函数
纯函数
纯函数
纯函数
附加作用
RxFeedback
附加作用
Redux
附加作用
Redux
纯函数
附加作用
纯函数
Redux
纯函数
附加作用
RxFeedback
Redux
ReactorKit