27-响应式编程

158 阅读6分钟

Swift 响应式编程详解

目录


响应式编程概述

响应式编程(Reactive Programming,简称RP)是一种编程范式,于1997年提出,可以简化异步编程,提供更优雅的数据绑定。

什么是响应式编程?

响应式编程是一种面向数据流和变化传播的编程范式。它具有以下特点:

  • 异步编程简化:提供更优雅的异步数据处理方式
  • 数据绑定:支持数据的自动同步和更新
  • 函数式融合:通常与函数式编程结合,称为函数响应式编程(FRP)
  • 事件驱动:基于事件流的编程模型

响应式编程的优势

  1. 简洁的异步代码:避免回调地狱
  2. 统一的处理方式:用同一套API处理各种异步事件
  3. 更好的组合性:可以轻松组合各种操作
  4. 更少的状态管理:减少可变状态的复杂性

响应式编程框架

ReactiveCocoa (RAC)

ReactiveCocoa是Objective-C和Swift的响应式编程框架:

ReactiveX (Rx)

ReactiveX是跨语言的响应式编程框架:

ReactiveX 各语言版本
语言框架名描述
JavaRxJavaJava平台的响应式编程
KotlinRxKotlinKotlin的响应式编程
JavaScriptRxJSJavaScript的响应式编程
C++RxCppC++的响应式编程
SwiftRxSwiftSwift的响应式编程

RxSwift 介绍

RxSwift是ReactiveX的Swift版本,为Swift开发者提供了强大的响应式编程能力。

安装配置

CocoaPods 安装

1. Podfile 配置

use_frameworks!

target 'YourTargetName' do
    pod 'RxSwift', '~> 5'
    pod 'RxCocoa', '~> 5'
end

2. 命令行安装

pod repo update
pod install

3. 导入模块

import RxSwift
import RxCocoa

模块说明

模块描述
RxSwiftRx标准API的Swift实现,不包括任何iOS相关的内容
RxCocoa基于RxSwift,给iOS UI控件扩展了很多Rx特性

资源链接

RxSwift 核心角色

核心概念

RxSwift的核心包含以下角色:

  • Observable:负责发送事件(Event)
  • Observer:负责订阅Observable,监听Observable发送的事件
  • Event:事件,包含数据或状态信息
  • Operator:操作符,用于加工处理事件序列

事件类型

public enum Event<Element> {
    /// Next element is produced.
    case next(Element)
    
    /// Sequence terminated with an error.
    case error(Swift.Error)
    
    /// Sequence completed successfully.
    case completed
}

RxSwift中的事件有3种类型:

  1. next:携带具体数据
  2. error:携带错误信息,表明Observable终止,不会再发出事件
  3. completed:表明Observable终止,不会再发出事件

Observable 和 Observer

Observable(可观察序列)

Observable是RxSwift的核心,它代表一个可观察的序列。

// 基本概念
let observable = Observable<String>.create { observer in
    observer.onNext("Hello")
    observer.onNext("World")
    observer.onCompleted()
    return Disposables.create()
}

Observer(观察者)

Observer用于观察Observable发出的事件。

let observer = AnyObserver<String> { event in
    switch event {
    case .next(let value):
        print("接收到值: \(value)")
    case .error(let error):
        print("发生错误: \(error)")
    case .completed:
        print("序列完成")
    }
}

创建 Observable

基本创建方法

1. create 方法
let observable = Observable<Int>.create { observer in
    observer.onNext(1)
    observer.onNext(2)
    observer.onNext(3)
    observer.onCompleted()
    return Disposables.create()
}
2. just 方法
// 发送单个值
let observable = Observable.just(1)
3. of 方法
// 发送多个值
let observable = Observable.of(1, 2, 3)
4. from 方法
// 从数组创建
let observable = Observable.from([1, 2, 3])
5. timer 方法
// 定时器
let observable = Observable<Int>.timer(
    .seconds(3),
    period: .seconds(1),
    scheduler: MainScheduler.instance
)

高级创建方法

interval 方法
// 间隔发送
let observable = Observable<Int>.interval(
    .seconds(1),
    scheduler: MainScheduler.instance
)
range 方法
// 范围序列
let observable = Observable.range(start: 1, count: 5)
repeatElement 方法
// 重复元素
let observable = Observable.repeatElement("Hello")

创建 Observer

AnyObserver

let observer = AnyObserver<Int> { event in
    switch event {
    case .next(let data):
        print("数据: \(data)")
    case .completed:
        print("完成")
    case .error(let error):
        print("错误: \(error)")
    }
}

Observable.just(1).subscribe(observer).dispose()

Binder

let binder = Binder<String>(label) { label, text in
    label.text = text
}

Observable.just("Hello")
    .map { "值是: \($0)" }
    .bind(to: binder)
    .dispose()

扩展 Binder 属性

extension Reactive where Base: UIView {
    var hidden: Binder<Bool> {
        return Binder<Bool>(base) { view, value in
            view.isHidden = value
        }
    }
}

// 使用自定义Binder
let observable = Observable<Int>.interval(
    .seconds(1),
    scheduler: MainScheduler.instance
)

observable
    .map { $0 % 2 == 0 }
    .bind(to: button.rx.hidden)
    .disposed(by: bag)

事件处理

订阅事件

完整订阅
observable.subscribe(
    onNext: { value in
        print("next: \(value)")
    },
    onError: { error in
        print("error: \(error)")
    },
    onCompleted: {
        print("completed")
    },
    onDisposed: {
        print("disposed")
    }
).disposed(by: bag)
简单订阅
observable.subscribe { event in
    print(event)
}.disposed(by: bag)

数据绑定

observable
    .map { "数值是: \($0)" }
    .bind(to: label.rx.text)
    .disposed(by: bag)

状态监听

传统监听方案的问题

传统的状态监听方案包括:

  • KVO
  • Target-Action
  • Notification
  • Delegate
  • Block Callback

这些方案经常出现错综复杂的依赖关系,耦合性较高,还需要编写重复的非业务代码。

RxSwift 的优势

1. 按钮点击事件
button.rx.tap.subscribe(onNext: {
    print("按钮被点击了")
}).disposed(by: bag)
2. TableView 数据绑定
let data = Observable.just([
    Person(name: "Jack", age: 10),
    Person(name: "Rose", age: 20)
])

data.bind(to: tableView.rx.items(cellIdentifier: "cell")) { row, person, cell in
    cell.textLabel?.text = person.name
    cell.detailTextLabel?.text = "\(person.age)"
}.disposed(by: bag)

// 选中事件
tableView.rx.modelSelected(Person.self)
    .subscribe(onNext: { person in
        print("选中了: \(person.name)")
    }).disposed(by: bag)
3. KVO 监听
class Dog: NSObject {
    @objc dynamic var name: String?
}

let dog = Dog()

dog.rx.observe(String.self, "name")
    .subscribe(onNext: { name in
        print("name is \(name ?? "nil")")
    }).disposed(by: bag)

dog.name = "Larry"
dog.name = "WangWang"
4. 通知监听
NotificationCenter.default.rx
    .notification(UIApplication.didEnterBackgroundNotification)
    .subscribe(onNext: { notification in
        print("APP进入后台: \(notification)")
    }).disposed(by: bag)
5. 双向绑定
// UISlider 和 UITextField 的双向绑定
slider.rx.value
    .map { "当前数值是: \($0)" }
    .bind(to: textField.rx.text)
    .disposed(by: bag)

textField.rx.text
    .subscribe(onNext: { text in
        print("text is \(text ?? "nil")")
    }).disposed(by: bag)

// 设置初始值
Observable.just(0.8)
    .bind(to: slider.rx.value)
    .dispose()

ControlProperty 特性

诸如 UISlider.rx.valueUITextField.rx.text 这类属性值既是Observable又是Observer,它们是 RxCocoa.ControlProperty 类型。

Disposable 管理

什么是 Disposable?

每当Observable被订阅时,都会返回一个Disposable实例。当调用Disposable的dispose方法时,就相当于取消订阅。

取消订阅的方式

1. 立即取消订阅
observable.subscribe { event in
    print(event)
}.dispose()
2. 使用 DisposeBag
let disposeBag = DisposeBag()

observable.subscribe { event in
    print(event)
}.disposed(by: disposeBag)

// 当 disposeBag 销毁时,会自动调用所有 Disposable 的 dispose 方法
3. 使用 takeUntil
let _ = observable
    .takeUntil(self.rx.deallocated)
    .subscribe { event in
        print(event)
    }

// 当 self 销毁时,会自动取消订阅

DisposeBag 的使用

class ViewController: UIViewController {
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 所有订阅都添加到 disposeBag
        button.rx.tap
            .subscribe(onNext: {
                print("按钮被点击")
            })
            .disposed(by: disposeBag)
    }
    
    // 当 ViewController 被销毁时,disposeBag 也会被销毁
    // 所有相关的订阅都会被自动取消
}

实际应用

1. 登录表单验证

class LoginViewController: UIViewController {
    @IBOutlet weak var usernameTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var loginButton: UIButton!
    
    private let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let usernameValid = usernameTextField.rx.text.orEmpty
            .map { $0.count >= 3 }
            .share(replay: 1)
        
        let passwordValid = passwordTextField.rx.text.orEmpty
            .map { $0.count >= 6 }
            .share(replay: 1)
        
        let everythingValid = Observable.combineLatest(
            usernameValid,
            passwordValid
        ) { $0 && $1 }
        
        everythingValid
            .bind(to: loginButton.rx.isEnabled)
            .disposed(by: disposeBag)
        
        loginButton.rx.tap
            .subscribe(onNext: {
                print("登录按钮被点击")
            })
            .disposed(by: disposeBag)
    }
}

2. 网络请求

func fetchUserData() -> Observable<User> {
    return Observable.create { observer in
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                observer.onError(error)
                return
            }
            
            if let data = data {
                do {
                    let user = try JSONDecoder().decode(User.self, from: data)
                    observer.onNext(user)
                    observer.onCompleted()
                } catch {
                    observer.onError(error)
                }
            }
        }
        
        task.resume()
        
        return Disposables.create {
            task.cancel()
        }
    }
}

// 使用
fetchUserData()
    .observeOn(MainScheduler.instance)
    .subscribe(
        onNext: { user in
            print("用户信息: \(user)")
        },
        onError: { error in
            print("错误: \(error)")
        }
    )
    .disposed(by: disposeBag)

3. 搜索功能

searchTextField.rx.text.orEmpty
    .throttle(.milliseconds(300), scheduler: MainScheduler.instance)
    .distinctUntilChanged()
    .flatMapLatest { query -> Observable<[SearchResult]> in
        if query.isEmpty {
            return Observable.just([])
        }
        return searchAPI(query: query)
    }
    .observeOn(MainScheduler.instance)
    .bind(to: tableView.rx.items(cellIdentifier: "cell")) { row, result, cell in
        cell.textLabel?.text = result.title
    }
    .disposed(by: disposeBag)

最佳实践

1. 使用 DisposeBag 管理订阅

class ViewController: UIViewController {
    private let disposeBag = DisposeBag()
    
    // 所有订阅都应该添加到 disposeBag
}

2. 避免循环引用

// 错误的做法
observable.subscribe(onNext: { [unowned self] value in
    self.updateUI(value)
}).disposed(by: disposeBag)

// 正确的做法
observable.subscribe(onNext: { [weak self] value in
    self?.updateUI(value)
}).disposed(by: disposeBag)

3. 使用操作符简化代码

// 使用 map 转换数据
observable
    .map { $0 * 2 }
    .filter { $0 > 10 }
    .subscribe(onNext: { value in
        print(value)
    })
    .disposed(by: disposeBag)

4. 合理使用 share()

let sharedObservable = observable.share(replay: 1)

sharedObservable
    .subscribe(onNext: { print("Observer 1: \($0)") })
    .disposed(by: disposeBag)

sharedObservable
    .subscribe(onNext: { print("Observer 2: \($0)") })
    .disposed(by: disposeBag)

5. 错误处理

observable
    .catchErrorJustReturn("默认值")
    .subscribe(onNext: { value in
        print(value)
    })
    .disposed(by: disposeBag)

总结

响应式编程为iOS开发带来了新的编程范式,RxSwift作为Swift的响应式编程框架,提供了强大而优雅的解决方案。

关键点回顾

  • ObservableObserver 是RxSwift的核心概念
  • DisposeBag 是内存管理的关键
  • 操作符 提供了强大的数据处理能力
  • 绑定 简化了UI更新逻辑

响应式编程的优势

  1. 统一的异步处理:用同一套API处理各种异步事件
  2. 声明式编程:关注数据流而不是控制流
  3. 减少状态管理:避免复杂的状态同步
  4. 更好的可测试性:纯函数式的操作更容易测试

学习建议

  1. 从简单开始:先掌握基本的Observable和Observer
  2. 实践操作符:通过实际项目学习各种操作符
  3. 理解内存管理:正确使用DisposeBag避免内存泄漏
  4. 循序渐进:逐步将现有项目迁移到响应式编程

注意:响应式编程有一定的学习曲线,建议从简单的场景开始,逐步掌握更复杂的用法。合理使用响应式编程能够让代码更加清晰和易维护。