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<String> { ... }

    /// 通过 token 取得用户信息
    static func userInfo(token: String) -> Observable<UserInfo> { ... }
}
/// 通过用户名和密码获取用户信息
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<Post> { ... }

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

/// 同时取得帖子列表和未读消息数量
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<T> 产生事件,可监听可观察

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

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

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
}

更好理解:我们可以把每一个 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<String?> {
        value
    }
    
    /// Reactive wrapper for `text` property.
    public var value: ControlProperty<String?> {
        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<T>(file: String = #file, function: String = #function, line: Int = #line, _ message: T, color: UIColor = .white) {
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容