RxSwift的第一印象

声明:本文由本人独立翻译完成,同步发表在稀土掘金

去年整整一年,我都在试图理解响应式编程的原理是什么,并且试图验证如果在我的app中使用这种编程范式是否会带来好处。于是,我查询了许多相关的解决方案,从ReactiveCocoa & Objective-C开始,及其Swift版本ReactiveCocoa with Swift,再到我朋友实现的一个轻量级的框架VinceRP。上述这些都是令人赞叹不已的项目,ReactiveCocoa的项目成熟度非常高,但是十分复杂;而VinceRP的实现非常容易,所以理解起来非常简单。

在学习的过程中,我写了一系列关于我学习响应式编程的经历的文章,所以经常会被读者问到一些关于RxSwift的问题。惭愧地说,我还从没有使用RxSwift来编写一个项目。实际上我还从来没用过任何语言的Rx框架,所以我一直认为,对于那些曾在别的开发环境中有使用Rx经历的人来说,理解RxSwift是非常容易的。既然如此,我也是时候来尝试一把了。

Rx

Rx是最常使用的一个响应式编程框架。它与其他RP框架的一大不同是它的跨平台特性,同时,它有着最大的开源社区,无数的文档以及有参考价值的问题讨论,许许多多的人不断地对其进行改进。

Swift

这门语言在去年一年中飞速的成长,并且现在也进行了开源了。一些像RxSwift之类的项目也随着其一起成长。因此,没有什么理由可以再阻止你去使用这些框架。当然,一些重大的改动仍然被列在radar上,但它们很可能在短时间内不会被解决,这就意味着这个项目会不断地被改进,这不是很好吗?

使用RxSwift开发一个app

如果你从未阅读过我的博客,可能你现在会猜我使用RxSwift开发了一个app。没错,你是对的。这是个很耗时的习惯,但是我不喜欢依赖于一个理想的环境,所以通常我都会写一个例子来让我有那么一点感觉。通过这种方式,我可以学会如何让成功得运行这个框架。(意译:这里我想说一点个人感受,对于解决问题来说,你所选用的框架只是万千可用方案中的一种,因此,方案的选择是因人而异的。而这些选择所带来的多样性,正是我如此热爱编程的一大原因。)

我所写的这个应用名叫iCopyPasta,是一个在去年Functional Swift Conf上展示的免费Mac剪贴板应用CopyPasta的iOS姐妹版。显而易见,它们并不是一个完整的产品所以并不可以被用来上架。我现在每天都使用Mac版本的CopyPasta,但是我可能存在某些偏见。我的计划是将来会发布Mac版本和iOS版本的CopyPasta应用,并可能会将这两个版本进行打通。

难道这不是我一直以来的计划吗?

Observables

我首先对UIPasteboard注册了观察者。 这些观察者会对你拷贝东西时出现在UIPasteboard中的字符串图像类型进行观察。

let pasteboard = NSNotificationCenter.defaultCenter().rx_notification("UIPasteboardChangedNotification", object: nil)
_ = pasteboard.map { [weak self] (notification: NSNotification) -> PasteboardItem? in
    if let pb = notification.object as? UIPasteboard {
        if let string = pb.valueForPasteboardType(kUTTypeUTF8PlainText as String) {
            return self?.pasteboardItem(string)
        }
        if let image = pb.valueForPasteboardType(kUTTypeImage as String) {
            return self?.pasteboardItem(image)
        }
    }
    return nil
}

之前我的方法是直接对UIPasteboard中的字符串图像直接进行观察,但是这个方法是不正确的。原因在于UIPasteboard可能不是一个KVO安全的类型(具体请看下方的评论)。参考别人的建议后,我使用RxSwift另一个非常棒的功能rx_notification来监听UIPasteboardChangedNotification

.subscribeNext { [weak self] pasteboardItem in
    if let item = pasteboardItem {
        self?.addPasteboardItem(item)
    }
}

这里的pasteboard是一个Observable<NSNotification>,这也是为什么可以很容易得订阅其.Next事件同时相应地去更新tableView。而map则是从监听到的通知所涉及的对象中获取字符串或者图像,并将获取到的结果转换成PasteboardItem

Dispose bags

订阅信号会产生Disposable。如果不终止订阅,那么这些生成的Disposable将会一直存在,这无疑是非常耗内存的。所以,你要么对这些订阅调用dispose,要么你可以像我一样,使用dispose bags来自动销毁相关的订阅。

.addDisposableTo(disposeBag)

UIKit/Appkit bindings

你可以很容易地通过rx_itemsWithCellIdentifierObservable序列绑定到table view上。element来自于我定义的PasteboardItem枚举类型,这也是为什么我会采用Switch来处理这个对象,这样可以根据其具体的枚举值来显示不同的样式。

pasteViewModel.pasteboardItems()
    .bindTo(tableView.rx_itemsWithCellIdentifier("pasteCell", cellType: UITableViewCell.self)) { (row, element, cell) in
     switch element {
     case .Text(let string):
         cell.textLabel?.text = String(string)
     case .Image(let image):
         cell.imageView?.image = image
}.addDisposableTo(disposeBag)

另外一个很棒的补充是rx_modelSelected。你可以通过它来获取你触发选择事件时对应的element。简单来说,它是一个对tableView:didSelectRowAtIndexPath:的封装,可以将代码变得非常简洁。

tableView
    .rx_modelSelected(PasteboardItem)
    .subscribeNext { [weak self] element in
        self?.pasteViewModel.addItemsToPasteboard(element)
    }.addDisposableTo(disposeBag)

你可以通过如下链接来查看所以关于UIKit/AppKit(RxCocoa)的扩展RxSwift’s GitHub

总体感受

到目前为止,我还只是探索了RxSwift能力的一小部分,但是我已经感受到RxSwift是一个非常棒的框架。如果能够更深入理解它的机制并学会基于它的设计思路进行思考,那肯定会更好。

我非常喜欢一些像Rx.playgroundRxMarbles这样的资料及great community这样的社区。这些资料给了我很多的灵感,所以我也乐于将我的学习经验分享给bitrise.io的用户。还有一些比较重要的内容,比如schedulers还未被涉及,但是绝对值得研究一番。

对我来说,我还需要一段时间来更好地理解Rx。与我尝试ReactiveCocoa只有个把小时不同,我现在可以每天都在工作中使用RxSwift,并且坚持使用超过了一年。这都得感谢在Prezi的伙伴们.

作为一个曾经学习过ReactiveCocoa的人来说,我现在更倾向于使用RxSwift,可能是因为我现在自认为已经对于RxSwift已经足够了解,并且使用它可以很快得完成我的编码任务。当然,在将来我可能会同时使用两者,但是我认为对于两者之间任一框架的熟练使用不代表会在学习另外一个框架的时候给你带来很大的优势。它们在几个方面有着不同。同时,这两个框架(概括来说应该是所有的响应式编程框架)都有着陡峭的学习曲线。对于我来说,我已经度过了学习ReactiveCocoa最难的那段时光,但如果你是一个初学者,我建议你自己动手尝试这两种框架,甚至更多。

深入阅读

如果你还在思考应该使用哪个响应式编程的框架,那么我建议你去读一读Ash Furrow所写的关于如何挑选响应式编程框架的文章

你也可以看看其他一些在iOS中使用响应式编程的视频及文章,这些内容都非常得棒,相信你会受益匪浅。