Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

改造集成QuietModemKit(下)——RxSwift学习(三)&超声波传输(二) #104

Open
soapgu opened this issue Jan 26, 2022 · 0 comments
Labels

Comments

@soapgu
Copy link
Owner

soapgu commented Jan 26, 2022

好了废话不多,直接接上篇

  • 热身一下

先拿UITextField做一下RX结合操作
RxCocoa为相关的UI控件做了包装处理

txtSend.rx.text.changed.subscribe{
            txt in
            print(txt)
        }.disposed(by: disposeBag)

这样就订阅了整个text控件的输入框变化

2022-01-26 16:50:34.831229+0800 Quiet4Swift[3323:1676014] Metal API Validation Enabled
next(Optional(""))
next(Optional("我"))
next(Optional("我"))
next(Optional("我c"))
next(Optional("我ce"))
next(Optional("我ce s"))
next(Optional("我ce sh"))
next(Optional("我ce sh h"))
next(Optional("我ce s h h"))
next(Optional("我ce sh h"))
next(Optional("我测试h"))
next(Optional("我ce s h h"))
next(Optional("我ce sh h"))
next(Optional("我ce sh"))
next(Optional("我测试"))
next(Optional("我测试"))
next(Optional("我测试y"))
next(Optional("我测试yi"))
next(Optional("我测试yi x"))
next(Optional("我测试yi xi"))
next(Optional("我测试yi xi w"))
next(Optional("我测试yi xiw"))
next(Optional("我测试一下"))
next(Optional("我测试一下"))

👌,但是我并不想这么敏锐,我只是要捕获相对静态一点的稳定的值,比如停止输入后2秒输入不再变化以后再输出要怎么处理那?

txtSend.rx.text.changed.flatMapLatest{
            new in
            Single<Int>
                .timer(RxTimeInterval.seconds(2), scheduler: MainScheduler.instance)
                .map{
                    _ in new
                }
                .debug()
        }
        .subscribe{
            txt in
            print(txt)
        }.disposed(by: disposeBag)

思路还是利用正投影,每次输入还处于不稳定态的化,就会被过滤掉。加debug可以看得更清楚一些

2022-01-26 17:07:25.833971+0800 Quiet4Swift[3334:1681584] Metal API Validation Enabled
2022-01-26 17:07:26.779: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:28.031: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:28.031: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:28.034: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:28.035: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:28.729: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:28.729: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:28.732: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:28.732: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:28.995: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:28.996: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:28.998: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:28.999: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:29.207: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:29.208: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:29.210: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:29.211: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:30.051: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:30.051: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:30.206: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:30.206: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:30.405: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:30.406: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:30.556: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:30.556: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:30.706: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:30.707: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:30.955: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:30.956: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:31.298: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:31.299: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:31.301: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:31.301: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:33.304: ViewController.swift:67 (initControls()) -> Event next(Optional("我也不知道🤷‍♀️沟沟壑壑火锅"))
next(Optional("我也不知道🤷‍♀️沟沟壑壑火锅"))
2022-01-26 17:07:33.305: ViewController.swift:67 (initControls()) -> Event completed
2022-01-26 17:07:33.305: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:48.842: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:48.922: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:48.922: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:49.059: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:49.060: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:49.209: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:49.209: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:49.342: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:49.343: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:49.476: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:49.476: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:49.592: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:49.593: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:49.708: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:49.709: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:49.826: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:49.826: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:49.959: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:49.960: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:50.076: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:50.076: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:50.209: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:50.209: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:51.066: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:51.066: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:51.069: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:51.069: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:51.566: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:51.566: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:51.569: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:51.569: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:51.981: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:51.982: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:51.984: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:51.984: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:52.332: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:52.333: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:52.336: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:52.336: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:52.782: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:52.783: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:52.785: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:52.786: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:53.233: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:53.233: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:53.236: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:53.236: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:53.649: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:53.649: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:53.652: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:53.652: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:54.115: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:54.116: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:54.119: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:54.119: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:54.665: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:54.666: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:54.668: ViewController.swift:67 (initControls()) -> isDisposed
2022-01-26 17:07:54.668: ViewController.swift:67 (initControls()) -> subscribed
2022-01-26 17:07:56.670: ViewController.swift:67 (initControls()) -> Event next(Optional("在家了一个星期没睡过懒觉"))
next(Optional("在家了一个星期没睡过懒觉"))
2022-01-26 17:07:56.671: ViewController.swift:67 (initControls()) -> Event completed
2022-01-26 17:07:56.671: ViewController.swift:67 (initControls()) -> isDisposed

基本达成目的,能输出相对稳定的字符了。但是timer被反复创建清理太多次了,性能上还有质疑
再加一层滤网节气阀 throttle

txtSend.rx.text.orEmpty
            .throttle(.milliseconds(1000), scheduler: MainScheduler.instance)
            .distinctUntilChanged()
            .flatMapLatest{
            t in
            Single<Int>
                .timer(.seconds(2), scheduler: MainScheduler.instance)
                .map{
                    _ in t
                }
                .debug()
        }
        .subscribe{
            txt in
            print(txt)
        }.disposed(by: disposeBag)

日志打印

2022-01-26 17:25:03.249: ViewController.swift:70 (initControls()) -> subscribed
2022-01-26 17:25:03.345303+0800 Quiet4Swift[3366:1689648] Metal API Validation Enabled
2022-01-26 17:25:05.253: ViewController.swift:70 (initControls()) -> Event next()
next()
2022-01-26 17:25:05.254: ViewController.swift:70 (initControls()) -> Event completed
2022-01-26 17:25:05.254: ViewController.swift:70 (initControls()) -> isDisposed
2022-01-26 17:25:05.740: ViewController.swift:70 (initControls()) -> subscribed
2022-01-26 17:25:06.742: ViewController.swift:70 (initControls()) -> isDisposed
2022-01-26 17:25:06.743: ViewController.swift:70 (initControls()) -> subscribed
2022-01-26 17:25:07.745: ViewController.swift:70 (initControls()) -> isDisposed
2022-01-26 17:25:07.746: ViewController.swift:70 (initControls()) -> subscribed
2022-01-26 17:25:08.747: ViewController.swift:70 (initControls()) -> isDisposed
2022-01-26 17:25:08.748: ViewController.swift:70 (initControls()) -> subscribed
2022-01-26 17:25:10.750: ViewController.swift:70 (initControls()) -> Event next(我在挑战黄鹤楼夺得)
next(我在挑战黄鹤楼夺得)
2022-01-26 17:25:10.751: ViewController.swift:70 (initControls()) -> Event completed
2022-01-26 17:25:10.751: ViewController.swift:70 (initControls()) -> isDisposed

有改善,可以滤掉一部分emit,不过必要的周期还是需要的,毕竟不能保证每一次的输入是最后一次

这部分只是热身练习下

  • UIPickerView的RxSwift改造

在以前的版本里面,我必须在UIViewController里实现UIPickerViewDataSource,UIPickerViewDelegate两个协议接口
实现4个方法
而且必须设置好dataSource和delegate

整体代码体检感不够好,有种松散的感觉

经过RxCocoa的整合一切将变得不同

Observable.just(["audible","ultrasonic-fsk","ultrasonic-fsk-fast"])
            .bind(to: pickerConfig.rx.itemTitles){
                _,item in
                return item
            }
            .disposed(by: disposeBag)

bind魔法直接让绑定UIPickerView一步到位

对于选中项的处理,原来需要实现didSelectRow,现在改成了这样

pickerConfig.rx.modelSelected(String.self)
            .subscribe(onNext: { [weak msgLabel] t in
                if let item = t.first {
                    msgLabel?.text = item
                }
            })
            .disposed(by: disposeBag)

直接可以转换成了RX对象来subscribe,一下简洁多了

其实我并不需要在切换config做处理,我只需要拿到当前配置值就行了

    func getConfigKey() -> String {
        let selectRow = pickerConfig.selectedRow(inComponent: 0)
        let selectItem: String = try! pickerConfig.rx.model(at: IndexPath(row: selectRow, section: 0))
        return selectItem
    }

需要稍微迂回下,先获取row的index
这里RxSwift也很贴心的给了扩展函数,提供可以获得绑定值。只是返回类型必须指定,否则会推断为Anytype,swift的泛型调用部门是不能声明<>这种类型的,反正是swift定的规矩,只能遵守。

RxSwift让我们把控件与代码的联系更加易读和紧密,那么它是怎么做到的那?

  • bind 魔法
   /**
    Subscribes to observable sequence using custom binder function and final parameter passed to binder function
    after `self` is passed.

        public func bind<R1, R2>(to binder: Self -> R1 -> R2, curriedArgument: R1) -> R2 {
            return binder(self)(curriedArgument)
        }

    - parameter binder: Function used to bind elements from `self`.
    - parameter curriedArgument: Final argument passed to `binder` to finish binding process.
    - returns: Object representing subscription.
    */
    public func bind<R1, R2>(to binder: (Self) -> (R1) -> R2, curriedArgument: R1) -> R2 {
        binder(self)(curriedArgument)
    }

比较绕,实际使用是,binder作为入参,传入,我们这次例子里面用的是pickerConfig.rx.itemTitles

curriedArgument是1用尾随闭包的方式作为入参传入,例子里是

            {
                _,item in
                return item
            }

binder(self)其实就是先执行一次解包操作,返回(R1) -> R2闭包再把curriedArgument传入再次执行闭包后返回R2

  • itemTitles魔法
         /**
         Binds sequences of elements to picker view rows.
         
         - parameter source: Observable sequence of items.
         - parameter titleForRow: Transform between sequence elements and row titles.
         - returns: Disposable object that can be used to unbind.
         
         Example:
         
            let items = Observable.just([
                    "First Item",
                    "Second Item",
                    "Third Item"
                ])
         
            items
                .bind(to: pickerView.rx.itemTitles) { (row, element) in
                    return element.title
                }
                .disposed(by: disposeBag)
         
         */
        
        public func itemTitles<Sequence: Swift.Sequence, Source: ObservableType>
            (_ source: Source)
            -> (_ titleForRow: @escaping (Int, Sequence.Element) -> String?)
            -> Disposable where Source.Element == Sequence {
                return { titleForRow in
                    let adapter = RxStringPickerViewAdapter<Sequence>(titleForRow: titleForRow)
                    return self.items(adapter: adapter)(source)
                }
        }

itemTitles的返回是一个闭包,titleForRow参数也是一个闭包,主要功能是提供picker的显示
里面嵌入了一个RxAttributedStringPickerViewAdapter

  • RxAttributedStringPickerViewAdapter的使命
final class RxAttributedStringPickerViewAdapter<Sequence: Swift.Sequence>: RxPickerViewSequenceDataSource<Sequence>, UIPickerViewDelegate {
    typealias AttributedTitleForRow = (Int, Sequence.Element) -> NSAttributedString?
    private let attributedTitleForRow: AttributedTitleForRow
    
    init(attributedTitleForRow: @escaping AttributedTitleForRow) {
        self.attributedTitleForRow = attributedTitleForRow
        super.init()
    }
    
    func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? {
        attributedTitleForRow(row, items[row])
    }
}

可以看到RxAttributedStringPickerViewAdapter实现了UIPickerViewDelegate,承担了提供Title的数据的任务

  • RxPickerViewDataSourceProxy的使命
extension RxPickerViewDataSourceProxy: UIPickerViewDataSource {
    /// Required delegate method implementation.
    public func numberOfComponents(in pickerView: UIPickerView) -> Int {
        (_requiredMethodsDataSource ?? pickerViewDataSourceNotSet).numberOfComponents(in: pickerView)
    }

    /// Required delegate method implementation.
    public func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        (_requiredMethodsDataSource ?? pickerViewDataSourceNotSet).pickerView(pickerView, numberOfRowsInComponent: component)
    }
}

正好实现了UIPickerViewDataSource协议,提供了数据源的标准接口方法

  • 小结
    所以说RxSwift其实帮助我们包装了UIPickerViewDataSource和UIPickerViewDelegate,省去了很多底层操作细节。可以让我们的代码更贴近一点业务逻辑代码。基础还是很重要,一步一步来不能随便“跳级”。另外RxSwift的源码真的好难啃,特别是嵌套闭包部分有“盗梦空间”的感觉
    比如下面这段
public func items<Sequence: Swift.Sequence, Cell: UICollectionViewCell, Source: ObservableType>
        (cellIdentifier: String, cellType: Cell.Type = Cell.self)
        -> (_ source: Source)
        -> (_ configureCell: @escaping (Int, Sequence.Element, Cell) -> Void)
        -> Disposable where Source.Element == Sequence {
}

嵌套闭包读起来很烧脑
图片

@soapgu soapgu added Demo Demo ReactiveX ReactiveX Swift labels Feb 7, 2022
soapgu added a commit that referenced this issue Feb 7, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant