RxSwift 基础使用

一、 使用 RxSwift 与传统方式对比

1. 按钮添加点击事件

传统方式

button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)

使用RxSwift
不再需要使用 Target Action

button.rx.tap.subscribe(onNext: {
        print("button Tapped")
    }) .disposed(by: disposeBag)

事件监听和处理在一个地方,更易维护

2. 代理

传统方式

class ViewController: UIViewController {
    ...
    override func viewDidLoad() {
        super.viewDidLoad()
        scrollView.delegate = self
    }
}

extension ViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        print("contentOffset: \(scrollView.contentOffset)")
    }
}

使用RxSwift后

 scrollView.rx.contentOffset
            .subscribe(onNext: { contentOffset in
                print("contentOffset: \(contentOffset)")
            })
            .disposed(by: disposeBag)

3. 通知

传统方式

var ntfObserver: NSObjectProtocol!

override func viewDidLoad() {
    super.viewDidLoad()
    // 添加通知
    ntfObserver = NotificationCenter.default.addObserver(
          forName: .UIApplicationWillEnterForeground,
          object: nil, queue: nil) { (notification) in
                print("Application Will Enter Foreground")
    }
}

// Remove
deinit {
    NotificationCenter.default.removeObserver(ntfObserver)
}

使用RxSwift后

  NotificationCenter.default.rx
        .notification(.UIApplicationWillEnterForeground)
        .subscribe(onNext: { (notification) in
        
            print("Application Will Enter Foreground")
            
        }) .disposed(by: disposeBag)

不再需要管理通知的生命周期

4. 多个任务之间有依赖关系

需求:先通过用户名密码取得 Token 然后通过 Token 取得用户信息,

/// 用回调的方式封装接口
enum API {

    /// 通过用户名密码取得一个 token
    static func token(username: String, password: String,
        success: (String) -> Void,
        failure: (Error) -> Void) { ... }

    /// 通过 token 取得用户信息
    static func userinfo(token: String,
        success: (UserInfo) -> Void,
        failure: (Error) -> Void) { ... }
}

/// 通过用户名和密码获取用户信息
API.token(username: "beeth0ven", password: "987654321", success: { token in
     
     API.userInfo(token: token, success: { userInfo in
                print("获取用户信息成功: \(userInfo)")
            }, failure: { error in
                print("获取用户信息失败: \(error)")
        })
        
    }, failure: { error in
    
        print("获取用户信息失败: \(error)")
})

如果使用RxSwift

/// 用 Rx 封装接口
enum API {

    /// 通过用户名密码取得一个 token
    static func token(username: String, password: String) -> Observable { ... }

    /// 通过 token 取得用户信息
    static func userInfo(token: String) -> Observable { ... }
}
/// 通过用户名和密码获取用户信息
API.token(username: "beeth0ven", password: "987654321")
    .flatMapLatest(API.userInfo)
    .subscribe(onNext: { userInfo in
        print("获取用户信息成功: \(userInfo)")
    }, onError: { error in
        print("获取用户信息失败: \(error)")
    })
    .disposed(by: disposeBag)

避免回调地狱

5. 等待多个并发任务完成后处理结果

需求: 需要将两个网络请求合并成一个

/// 用 Rx 封装接口
enum API {

    /// fetch feed list
    static func postList() -> Observable { ... }

    /// fetch unread count
    static func unreadCount() -> Observable { ... }
}

/// 同时取得帖子列表和未读消息数量
Observable.zip(
      API.postList(),
      API.unreadCount()
    ).subscribe(onNext: { (posts, unreadCount) in
        print("获取Feed成功: \(posts.count)")
        print("获取未读成功: \(unreadCount) ")
    }, onError: { error in
        print("获取失败: \(error)")
    })
    .disposed(by: disposeBag)

二、 基本思想

Sequences / Observables

Sequence协议

Swift中Array遵循了Sequence协议。
处理Array中的元素,并读取

let array = [1, 2, 3, 4, 5]

// Filter Map
let array2 = array.filter({ $0 > 1 }).map({ $0 * 2 })//4 6 8 10

// 创建迭代器
var indexGenerator = array2.makeIterator()

// Call next()
let fisrt = indexGenerator.next() // 4
let seoncd = indexGenerator.next() //6
可监听序列

RxSwift 基本思想与此类似。 RxSwift 生成的是一个可监听的序列,可异步监听。与Array不同,它是时间概念上的序列,不会同时触发。无状态。

三、RxSwift 核心概念

  1. Observable - 产生事件,可监听可观察
  2. Observer - 响应事件,观察者
  3. Operator - 创建变化组合事件,操作符
  4. Disposable - 管理绑定(订阅)的生命周期
  5. Schedulers - 线程队列调配

1. Observable 产生事件,可监听可观察

Observable 它的作用就是可以异步地产生一系列的 Event(事件),即一个 Observable 对象会随着时间推移不定期地发出 event(element : T) 这样一个东西。

而且这些 Event 还可以携带数据,它的泛型 就是用来指定这个Event携带的数据的类型。

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

更好理解:我们可以把每一个 Observable 的实例想象成于一个 Swift 中的 Sequence.

温度

2. Observer - 响应事件,观察者

观察者 是用来监听事件,它需要这个事件做出响应。
例如:弹出提示框就是观察者,它对点击按钮这个事件做出响应。

tap.subscribe(onNext: { [weak self] in
    self?.showAlert()
}, onError: { error in
    print("Error Happened: \(error.localizedDescription)")
}, onCompleted: {
    print("done")
})

创建观察者

2.1 直接接上Subscribe
2.2 Binder 特征观察者 usernameTxtField.rx.text

  1. 不会处理错误事件
  2. 确保绑定都是在给定 Scheduler 上执行(默认 MainScheduler)

extension Reactive where Base: UITextField {
    /// Reactive wrapper for `text` property.
    public var text: ControlProperty {
        value
    }
    
    /// Reactive wrapper for `text` property.
    public var value: ControlProperty {
        return base.rx.controlPropertyWithDefaultEvents(
            getter: { textField in
                textField.text
            },
            setter: { textField, value in
                // This check is important because setting text value always clears control state
                // including marked text selection which is imporant for proper input 
                // when IME input method is used.
                if textField.text != value {
                    textField.text = value
                }
            }
        )
    }

3. Operator - 创建变化组合事件,操作符

操作符可以帮助创建新的序列,或者变化组合原有的序列,从而生成一个新的序列。
.debunce 过滤掉高频产生的元素
.filter 按条件过滤

textField.rx.text
    .filter({$0.count > 3})
    .debounce(0.5, .mainScheduler)
    .subscribe{ }

操作符决策树:
http://reactivex.io/documentation/operators.html#tree

4. Disposable - 管理绑定(订阅)的生命周期

一个序列如果发出了 error 或者 completed 事件,那么所有内部资源都会被释放。
两种方式:

  1. 如果需要提前释放这些资源或取消订阅的话,可以对观察者返回的 可被清除的资源(Disposable) 调用 dispose
var disposeBag = DisposeBag()

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    textField.rx.text.orEmpty
        .subscribe(onNext: { text in print(text) })
        .disposed(by: self.disposeBag)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    self.disposeBag = DisposeBag()
}
  1. 使用 takeUntil 操作符,绑定生命周期到其他对象上
override func viewDidLoad() {
    super.viewDidLoad()
    
    _ = usernameValid
        .take(until: rx.deallocated)
        .bind(to: passwordOutlet.rx.isEnabled)
        

5. Schedulers - 线程队列调配

Schedulers 是 Rx 实现多线程的核心模块,它主要用于控制任务在哪个线程或队列运行。

// 后台取得数据,主线程处理结果
DispatchQueue.global(qos: .userInitiated).async {
   let data = try? Data(contentsOf: url)
   DispatchQueue.main.async {
       self.data = data
   }
}

在RxSwift 中

rxData
    .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { [weak self] data in
        self?.data = data
    })
    .disposed(by: disposeBag)

说明:
.subscribe(on: MainScheduler.instance)
决定数据序列的构建函数在哪个 Scheduler 上运行。

.observe(on: MainScheduler.instance)
决定在哪个 Scheduler 监听这个数据序列

  1. MainScheduler
    代表主线程,处理UI任务
  2. SerialDispatchQueueScheduler
    抽象了串行 DispatchQueue
  3. ConcurrentDispatchQueueScheduler
    抽象了并行 DispatchQueue
  4. OperationQueueScheduler
    抽象了 NSOperationQueue。可设置 maxConcurrentOperationCount 来控制最大并发数

四、基础使用

  1. 当用户输入用户名时,如果用户名不足 5 个字就给出提示语,并且无法输入密码,当用户名符合要求时才可以输入密码。
  2. 当用户输入的密码不到 5 个字时也给出红色提示语。
  3. 当用户名和密码有一个不符合要求时底部的绿色按钮不可点击,只有当用户名和密码同时有效时按钮才可点击。
  4. 当点击按钮后弹出一个提示框。
    Code

五、突出优势

  • 易复合 - 一堆Observable,随意组合
  • 高复用 - 因为它易复合
  • 清晰 - 代码量降低, 更专注在业务上

六、参考

  1. Rx 官网: http://reactivex.io/
  2. 教程: https://www.jianshu.com/p/f61a5a988590

Dev tips

  1. 在 protocol 里面加 变量
  2. 字面量表达式:
    image.png
public func print(file: String = #file, function: String = #function, line: Int = #line, _ message: T, color: UIColor = .white) {
}

你可能感兴趣的:(RxSwift 基础使用)