本文翻译自:Custom convenience operators with RxSwift, Part 1
原文地址: rx-marin.com/post/rxswif…
作者: Marin Todorov
介绍
就像学习一门新的语言一样,你需要建立一个字典(注1:原文:Dictionary, 可以理解为字典一样的东西)
来开始了解语言的工作原理。学习RxSwift
,你必须学习Rx
运算符,并且最终将他们全部掌握。
然后,一旦你对语言通用部分有了很好的理解能力,就可以开始想出新的想法,与此同时可以提高你演讲(注2: 原文expressiveness,可以理解为 代码的表达力 更合适)
的表现力和便利性。
我上周遇到了同样的事情,我第一次觉得我正在使用RxSwift
完成工作,因为我了解了很多operators
。当然,我希望创建自定义的convenience operators
,虽然他不会做基本的新的事情,但会帮助我更好更清楚的表达自己。
replaceWith(value): Replace any element with a constant(用常量替代任何元素)
当我只想对一组事件作出反应时,我将替代发出的实际值,以便我可以将两个或更多个Observables合并成一个流并观察该流。
以下的代码是上周发布的,并观察了定时器应用的开始和停止按钮上的点击:
let isRunning = Observable
.merge([btnPlay.rx.tap.map({ return true }), btnStop.rx.tap.map({ return false })])
.startWith(false)
.shareReplayLatestWhileConnected()
我正在看这一大块代码,并思考着应该有更加简洁,更加可读性的方式来表达map:
这部分。我在这里做的两个Observable
实际上是忽略实际的值,并用一个常量替换它。
所以我深入研究了一下RxSwift
代码,并根据我在那里找到的写了下面这段辉煌(注3:原文brilliant,其实作者是反义,可以理解为难看的)
的代码:
func replaceWith<R>(value: R) -> Observable<R> {
return Observable.create { observer in
let subscription = self.subscribe { e in
switch e {
case .Next(_):
observer.on(.Next(value))
case .Error(let error):
observer.on(.Error(error))
case .Completed:
observer.on(.Completed)
}
}
return subscription
}
}
我创建并返回一个新的Observable
,并完善了Error
和Completed
事件。但用常量值来替换Next
事件的值。但是这样好吗?
看起来像是宏伟的代码其实是有点过了。我的意思是,毕竟我只想map
任何类型的任何值到一个常量,当你这样说,几乎不用写代码。所以最后我重写了这样的代码:
extension ObservableType {
func replaceWith<R>(value: R) -> Observable<R> {
return map { _ in value }
}
}
正如你所看到的,我并没有对整个事情感到疯狂,只是从字面上看,我想在ObservableType
的一个扩展方法中重用并将其抽象出来。
从文章的开头的代码块现在看起来是这样的:
let isRunning = Observable
.merge([btnPlay.rx.tap.replaceWith(true), btnStop.rx.tap.replaceWith(false)])
.startWith(false)
.shareReplayLatestWhileConnected()
很棒。拥有自己的自定义convenience operator
使得代码更不容易出现错误,(闭包中不用写多余的代码),并且更加易于阅读。
在这一点上,我开始怀疑我自己,这简直太好了,不过不得不说真话的话,我一定是做错了什么:)
然而事实证明,许多人在代码的过程中都有过准确地自定义operators
,他显然解决了一个常见的问题。
然后我有点疯狂,决定在兴趣的驱动下进一步探索我可以深入到那里。
replaceWithDate(): 将时间戳替换为最新值
由于我的热情已经被replaceWith()
点燃了,我认为从convenience operator
中获取可观测序列中最新元素的时间戳是很有趣的。
在这个具体的情况下,我将使用常量替代具体的当前日期:
extension ObservableType {
func replaceWithDate<R>(value: R) -> Observable<NSDate> {
return map { _ in NSDate() }
}
}
现在我可以将Observable
中的最新值绑定到Label
,并在另一个Label
中显示该值的时间戳,如下所示:
let count = Observable<Int>
.interval(3, scheduler: MainScheduler.instance)
.shareReplay(1)
count.map {counter in "\(counter)"}
.bindTo(label1.rx_text)
.addDisposableTo(bag)
count.replaceWithDate()
.map {$0.description}
.bindTo(label2.rx_text)
.addDisposableTo(bag)
这是结果(等待几秒钟可以看到数字在增加):
negate():对元素的值取反
接下来我注意到有时我需要将一个Observable
绑定到一个按钮的rx.enabled
属性,有时候要绑定到rx.hidden
。在编写绑定代码时,我不得不使用许多map {value in!value}
,这使得我的代码很难以阅读。
如果你查看了上周的文章,你将看到,为了提高可读性,我最终有两个可观察值:一个称为
isRunning
,另一个"isntRunning"
。
在阅读了一些RxSwift
的代码之后,我学会了如何向某个类型的Observable
添加一个运算符。在我遇到的这个情形中,我想要给只产生Bool
的Observables
添加negate() operator
。
Observable
将其元素的类型暴露为Element,我可以很容易地将它与BooleanType
(Swift FTW!)
进行匹配:
extension Observable where Element: BooleanType {
public func negate() -> Observable<Bool> {
return map {value in !value}
}
}
Sweet - 感谢协议扩展与相关类型!现在我可以很容易地编写代码:
active.bindTo(btnStart.rx.enabled).addDisposableTo(bag) active.negate().bindTo(btnStart.rx.hidden).addDisposableTo(bag)
当活跃的Observable
发出true
元素时,此代码将同时启用和显示该按钮。Pretty sleek eh?
今天文章中的代码,我稍后会添加到我的项目中:
extension Observable where Element : SignedIntegerType {
public func negate() -> Observable<E> {
return map {value in -value}
}
}
现在,negate()
也可在其他上下文中工作。如果您在Observable<Bool>
上使用它,它将应用元素逻辑的not
值;如果您在Observable<Int>
上使用它,则会产生元素的负值。Cool!
结论
创建自己的convenience operator
是非常棒的。它可以使代码更易读,引入错误的几率也会减少,并且没有任何错误。
在我的下一篇文章中,我将探索更多的operator
。你想分享你的任何一个吗?
你知道一个更好的办法吗?或者看到一个bug?在Twitter上@我
注: 原文提供的代码下载地址还不是Swift 3的,所以我在这里已经更改了原作者提供的地址。代码经过
Xcode8.2.1
测试,完全可以测试通过。
对于英语好的同学,推荐阅读作者的英文原文。