代码示例中将传统的项目用 RxSwift 来改造。

使用 Variable 替代传统的变量

// var todoItems: [TodoItem] = []
let todoItems = Variable<[TodoItem]>([])

使用变量时,仅需要取todoItems.value.

这样做可以方便监听todoItems的值(信号)的变化,tableView 可以统一做刷新操作。

todoItems.asObservable()
    .subscribe(onNext: { [weak self] todos in
        self?.updateUI(todos: todos)
    })
    .disposed(by: bag)

额外的,如果改变 TodoItem 属性值,是不会触发信号变化的,这时我们需要手动触发一下。例如:

odoItems.value[indexPath.row] = newTodoItem

使用 Observer 进行 ViewController 之间的值传递(反向传值)

vc1 push TodoDetailViewController 为例.

TodoDetailViewController中声明:

// 为什么这里我们使用了一个Subject对象呢? 因为在TodoDetailViewController内部,我们需要一个Observer,它要订阅到UITextField和UISwitch的值
// 为什么这个Subject是一个PublishSubject呢? 我们也需要它是一个Observable,可以让TodoLisViewController订阅到之后更新Todo列表的显示
fileprivate let todoSubject = PublishSubject<TodoItem>()

// 为了避免todoSubject意外从TodoDetailViewController外部接受onNext事件,
// 我们把todoSubject定义成了fileprivate属性。对外,只提供了一个仅供订阅的Observable属性todo。
var todo: Observable<TodoItem> {
    return todoSubject.asObservable()
}

在 vc1 中进行订阅:

_ = currController.todo.subscribe(onNext: { [weak self] item in
    self?.todoItems.value.append(item)
}) {
    print("Finish adding a new todo.")
} // .disposed(by: currController.bag) 因为detaillvc里调用了complete,因此不需要做额外处理

为了进行资源释放,TodoDetailViewController页面图退出的时候,执行 complete,以便让订阅者自动取消订阅。

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

RxSwift 对某些方法返回方式的改造

func saveTodoItems() {
    let data = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWith: data)

    archiver.encode(todoItems.value, forKey: "TodoItems")
    archiver.finishEncoding()

    data.write(to: dataFilePath(), atomically: true)
}

我们知道data.write...并不一定能操作成功,因此改造为如下方式:

func saveTodoItems() -> Observable<Void> {
    let data = NSMutableData()
    let archiver = NSKeyedArchiver(forWritingWith: data)
    archiver.encode(todoItems.value, forKey: "TodoItems")
    archiver.finishEncoding()

    return Observable.create { ob -> Disposable in
        let res = data.write(to: self.dataFilePath(), atomically: true)
        if res {
            ob.onCompleted()
        } else {
            ob.onError(SaveTodoError.cannotSaveToLocalFile)
        }
        return Disposables.create()
    }
}

方法的调用如下:

_ = saveTodoItems().subscribe(onError: { error in
        print(error)
    }, onCompleted: {
        print("on completed")
    }, onDisposed: {
        print("on disposed")
    }) // .disposed(by: bag) 因为会complete 因此不需要 add bag 。

用户授权的灵活配置

这里以用户获取图片读取权限为例:

import Foundation
import Photos
import RxSwift

extension PHPhotoLibrary {
    static var isAuthorized: Observable<Bool> {
        return Observable.create { observer in
            // 避免在自定义的事件序列中影响其它Observable的订阅,甚至是把整个UI卡住
            DispatchQueue.main.async {
                if authorizationStatus() == .authorized {
                    observer.onNext(true)
                    observer.onCompleted()
                } else {
                    // Notify the subscriber of the authorization failure
                    // event explicitly. We may omit this event by default.
                    observer.onNext(false)
                    requestAuthorization {
                        observer.onNext($0 == .authorized)
                        observer.onCompleted()
                    }
                }
            }
            return Disposables.create()
        }
    }
}

判断用户授予权限时,进行页面的刷新

let isAuthorized = PHPhotoLibrary.isAuthorized
    isAuthorized
        .skipWhile { $0 == false } // 跳过授权失败的部分
        .take(1)
        .observeOn(MainScheduler.instance) // 保证在主线程执行订阅代码
        .subscribe(onNext: { [weak self] _ in
            self?.photos = PhotoCollectionViewController.loadPhotos()
            self?.collectionView.reloadData()
        })
        .disposed(by: bag)

判断用户拒绝给与权限或者当前没有图片权限时,给与提示:

// 用户授权失败的情况处理
// 因为它对应的事件序列只有一种情况:.next(false),.next(false),.completed。因此,我们只要对事件序列中所有元素去重之后,订阅最后一个.next事件
isAuthorized
    .distinctUntilChanged()
    .takeLast(1)
    .filter { $0 == false }
    .subscribe { [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)
        })
    }
    .disposed(by: bag)