RxSwift 6 Filter Operator 实操

1 优化现有应用

前面一章引入了 RxSwift 中的函数式编程的方面, 即 Operator.

下面就来使用之前学过的一些过滤型操作符, 看看在实际工程中如何使用它们.

当然下面的这些代码只是引路作用, 并非是进入所谓的 "操作符最佳实践". 先入门, 然后再慢慢提高优化!

2 共享 Observable 序列, 实现一些过滤操作

先来看一个情况:

let numbers = Observable.create { observer in
    let start = getStartNumber()
    observer.onNext(start)
    observer.onNext(start+1)
    observer.onNext(start+2)
    observer.onCompleted()
    return Disposables.create()
}

getStartNumber() 方法是这样的:

var start = 0
func getStartNumber() -> Int {
start += 1
    return start
}

下面对该序列进行观察.

numbers
  .subscribe(onNext: { el in
    print("element [\(el)]")
  }, onCompleted: {
    print("-------------")
  })

如果多次观察这段代码, 最后的输出会在每次观察的时候都不一样.

这是为什么呢?

因为每次观察者开始观察的时候, 都会去执行 Create 块中的代码, 从而导致序列的变化.

有时这样的变化是有意为之, 但有时却不希望发生.

如果不希望每次观察都是去重新执行 create 块, 则可以使用 share() 操作符.

let newPhotos = photosViewController.selectedPhotos
  .share()

这样的话, 任何在 newPhotos 上的新观察者得到的都是同样的可观察序列.

实际上的原理是这样的: 当序列的观察者数量从 0 变化到 1 的时候, share 会去创建一个 Observable 序列出来. 当观察者再增加时, share 则会直接使用已创建的序列交给观察者. 如果在该序列上的所有观察者都被释放了. 则 share 会将创建出来的序列销毁. 而如果后面又有新的观察者, 则又会重复上述过程.

另外 share 操作符有一个特性, 即它不会提供在观察者开始观察之前序列已发射的内容. 如果需要 share 的共享特性, 又需要知道最后一个发射的内容, 则可以使用 shareReply(_) 操作符, 它可以指定一个缓存, 缓存之前发射过的若干事件.

2.1 ignoreElments 操作符

这个操作符的作用是只允许 complete 或 error 通过. 这样的话, 就可以用来观察完成或是错误. 当然这里只是一个假设的使用情况, 不对 next 事件作出响应也是一个结果.

2.2 实现过滤掉相同图片的功能

另外如果想添加照片的时候只添加不同的文件名的图片, 则也可以使用过滤.

但是多个 UIImage 对象间的区别不能通过地址来识别, 也无法通过名字或 URL 识别.(除非是自定义的子类. 实际使用的时候就可以继承并在子类中添加类似属性用于识别不同的 Image.)

不过在本例中仅使用字节长度作为判断依据, 防止跑题.

newPhoto
    .filter({ newImage in
        // 过滤竖向的图片.
        newImage.size.width > newImage.size.height
    })
    .filter({ [weak self] newImage in
        // 过滤相同大小的图片, 防止重复选择
        let len = UIImagePNGRepresentation(newImage)?.count ?? 0
        guard self?.imageCache.contains(len) == false else { return false }
        self?.imageCache.append(len)
        return true
    })
    .subscribe(onNext: { [weak self] newImage in
        guard let images = self?.images else { return }
        images.value.append(newImage)
        }, onDisposed: {
            print("completed photo selection")
    })
    .addDisposableTo(photosViewController.bag)

2.3 实现当只有满足条件时才会允许 next 消息通过

这里需要使用 takeWhile 操作符. 可以为 takeWhile 提供一个布尔判断, 如果值变为假之后, 就可以取消掉之后的所有元素.

newPhoto
    .takeWhile({ [weak self] _ in
        return (self?.images.value.count ?? 0) < 6
    })
    // ... 下面还是之前的过滤代码等内容

这样一来, 条件为 false 的时候, 就不会允许 next 通过了.

3 优化照片选择器

下面首先来构造一个自定义 observable, 然后通过不同的过滤器来操作它, 提升用户体验:

这个 observable 是针对某个权限的请求和允许情况的:

import Foundation
import Photos
import RxSwift

extension PHPhotoLibrary {
    static var authorized: Observable {
        return Observable.create({ observer in
            DispatchQueue.main.async {
                if authorizationStatus() == .authorized {
                    observer.onNext(true)
                    observer.onCompleted()
                } else {
                    observer.onNext(false)
                    requestAuthorization({ status in
                        observer.onNext(status == .authorized)
                        observer.onCompleted()
                    })
                }
            }
            return Disposables.create()
        })
    }
}

上面的代码中, 当外界有新的观察者开始观察的时候, 都会触发 create 方法的执行. 所以每次都会去判断权限.

使用 DispatchQueue.main.async 的意思是: 首先 DispatchQueue.main 表示在主线程中执行, 而 DispatchQueue.global() 是在后台线程中执行, 且 sync 表示当前线程会等待该工作块的执行完毕后再继续执行, 而 async 的话, 当前线程不会等待该工作块的执行完毕.

sync 方式执行的工作块, 当前线程会等待该工作块执行完毕后再继续执行. 而 async 方式执行的工作块当前线程是不会等待它结束的.

3.1 开始外界的观察操作

下面就在外界开始观察, 若权限是允许的情况下, 则重新加载照片.

由于权限的请求只能一次, 故当前状态有两种:

  • 用户第一次运行程序, 第一次请求权限, 且点击的是 ok.

    false---true---completed

  • 之后的运行过程, 如果之前允许过该权限.

    true---completed

经过分析, 我们知道要重新加载照片的前提就是遇到 true, 而 true 的事件肯定是最后一个 next 事件.

下面就开始观察:

let authorized = PHPhotoLibrary.authorized.share()
authorized.skipWhile({ elem in
    elem == false
}).take(1).subscribe(onNext: { [weak self] _ in
    self?.photos = PhotosViewController.loadPhotos()
    DispatchQueue.main.async {
        self?.collectionView?.reloadData()
    }
}).addDisposableTo(bag)

不过在 RxSwift 中尽可能不要使用 GCD 来切换线程, 更多地是使用 scheduler 来达到目的. 详见 15章.

3.2 当用户不允许时显示错误信息

每次这样的情况都需要先分析好当前的 Observable 中的事件序列是个什么样的情况.

4 利用时间的操作符

略看.

第六章结束.

你可能感兴趣的:(RxSwift 6 Filter Operator 实操)