使用RxSwift更好地处理授权提示

把用户授权结果变成一个 Observable

由于 iOS 的用户授权动作是异步的, 因此在请求授权的同时已经在加载资源了, 所以会造成通过授权但是会启动白屏现象; 为了能在用户完成授权操作之后继续更新UI,我们得先把授权的结果封装成一个Observable。

实际上用户授权结果只有两种情况:

  • 如果用户已经授权, 事件序列就是:.next(true),.completed()
  • 如果用户未授权,序列的第一个事件就一定是.next(false)。然后,如果用户拒绝授权,序列中的事件就是:.next(false).completed。否则,就是.next(true).completed

有了上面的思路, 就可以给 PHPhotoLibrary 添加一个 extension:

// PHPhotoLibrary+Rx.swift
extension PHPhotoLibrary {
static var isAuthorized: Observable<Bool> {
return Observable.create { observer in
DispatchQueue.main.async {
if authorizationStatus() == .authorized {
observer.onNext(true)
observer.onCompleted()
} else {
requestAuthorization {
observer.onNext($0 == .authorized)
observer.onCompleted()
}
}
}
return Disposables.create()
}
}
}

上面的代码用到了PHPhotoLibrary的两个 API:

  • authorizationStatus获取当前授权状态;
  • requestAuthorization申请用户授权;

不过有一个需要注意的地方需要是需要把通知observer的代码放进DispatchQueue.main.async中; 这是是为了避免在自定义的事件序列中影响其它Observable的订阅,甚至是把整个UI卡住。

订阅用户授权结果

订阅的部分, 应该写在你要请求授权的 ViewController 中的viewDidLoad方法中, 但是我们并不能直接订阅isAuthorizedonNext然后处理 true 和 false 的情况, 因为单一的时间并不能反应授权的情况, 他有以下两种情况:

  • 授权成功的序列可能是:.next(true).completed.next(false).next(true).completed
  • 授权失败的序列是: .next(false), .next(false), .completed

订阅请求授权成功事件

从上面的分析可以得出, 要订阅授权成功的事件, 我们只需要忽略事件序列中的false, 然后读取到第一个true就可以判断授权成功了; 可以使用一个过滤型的时间处理来完成上面的工作:

override func viewDidLoad() {
super.viewDidLoad()
let isAuthorized = PHPhotoLibrary.isAuthorized
isAuthorized
.skipWhile { $0 == false }
.take(1)
.subscribe(onNext: {
[weak self] _ in
// Reload the photo collection view
if let `self` = self {
self.photos = PhotoCollectionViewController.loadPhotos()
DispatchQueue.main.async {
self.collectionView?.reloadData()
}
}
})
.addDisposableTo(bag)
}

注意上面的处理 UI 的动作也需要在主线程中执行

不过,实际上,我们并不经常在RxSwift的订阅代码里使用GCD,RxSwift提供了一个更简单的机制,叫做scheduler, 我们可以把上面的事件订阅的代码修改成:

isAuthorized
.skipWhile { $0 == false }
.take(1)
.observeOn(MainScheduler.instance)
.subscribe(onNext: {
[weak self] _ in
// Reload the photo collection view
if let `self` = self {
self.photos = PhotoCollectionViewController.loadPhotos()
self.collectionView?.reloadData()
}
})
.addDisposableTo(bag)

.observeOn(MainScheduler.instance)就是表示在主线程中执行订阅代码;

订阅授权失败事件

由于授权失败只有一种情况就是: .next(false).next(false).completed, 所以可以对事件序列中的所有元素去重, 然后订阅最后一个next事件, 如果是false, 就表示授权失败了:

isAuthorized
.distinctUntilChanged()
.takeLast(1)
.filter { $0 == false }
.subscribe(onNext: { [weak self] _ in
self?.flash(title: "Cannot access your photo library",
message: "You can authorize access from the Settings.",
callback: { [weak self] _ in
self?.navigationController?.popViewController(animated: true)
})
})
.addDisposableTo(bag)