适用于 Swift 的高效、易扩展的反应式框架。
该框架目前支持 iOS 和 Mac。
tvOS 和 watchOS 尚未进行测试/支持。
让我们看看一个基本的例子
import VinceRP
// Define reactive sources
// -----------------------
// integer-stream object with a starting value of 1
let s1 = reactive(1)
// integer-stream object with a starting value of 2
let s2 = reactive(2)
// Define a calculated variable
// ----------------------------
// whenever s1 or s2 receives a new value (via the <- operator)
// the value of this variable gets recalculated automagically
// using the the block after 'definedAs'
let sum = definedAs{ s1* + s2* }
// Remember sum* is just a syntactic sugar for sum.value()
// -------------------------------------------------------
// * reads the last / current value of sum
print(sum*) // 3
// Push a new value into the stream
// --------------------------------
// s.update(3)
s2 <- 3
// it recalculates using the block and push a new value into the stream
print(sum*) // 4
请注意 - 多亏了 Swift 的类型推断 - 它还推断出源的类型和 sum
。所以下面的代码无法编译
import VinceRP
let s = reactive(1)
let s2 = definedAs{ "s*2 = \(s.value() * 2)" }
let s3 = definedAs{ s2* + s* }
然而,XCode 给出了一个奇怪的错误信息,它关于缺少的 <string> + <int>
操作符。
当然,您也可以有副作用
import VinceRP
// Define a reactive stream variable
let s = reactive(1)
var counter = 0
// Whenever 's' changes the block gets called
onChangeDo(s) { _ in
counter++
}
// 1 because of the initialization
print(counter) // 1
如果您不想计数初始化
import VinceRP
// Define a reactive stream variable
let s = reactive(1)
var counter = 0
// Whenever 's' changes the block gets called
onChangeDo(s, skipInitial:true) { _ in
counter++
}
// Push a new value into the stream
s <- 2
// 1 because of the update
print(counter) // 1
如果您对错误感兴趣
import VinceRP
// Define a reactive stream variable
let s = reactive(1)
s.onChange(skipInitial: true) {
print($0)
}.onError {
print($0)
}
// Push a new value triggers the 'onChange' block
s <- 2
// Push an error triggers the 'onError' block
s <- NSError(domain: "test error", code: 1, userInfo: nil)
// output:
// 2
// Error Domain=test error Code=1 "(null)"
它适用于 Bool
流,并否定值。
// Define a reactive stream variable with 'true' as initial value
let a = reactive(true)
// It's value is true
print(a.value()) // true
// If you apply the 'not()' operator it negates all the values of the stream
print(a.not().value()) // false
// Define a reactive stream variable
let x = reactive(1)
// Define a calculated variable and apply 'skipErrors'
let y = definedAs { x* + 1 }.skipErrors()
// Let's count the number of errors
var count = 0
onErrorDo(y) { _ in
count++
}
// When we push an error into x
x <- NSError(domain: "domain.com", code: 1, userInfo: nil)
// Because 'y' ignores errors
print(count) // 0
// Define a reactive stream variable with a starting value of 1
let x = reactive(1)
// This array will represent the history of 'x'
var history = [Int]()
// If 'x' receives a new value...
x.foreach {
history.append($0)
}
// Then
print(history) // [1]
// Push a new value
x <- 2
// Then
print(accu) // [1, 2]
// Define a reactive stream variable with a starting value of 1
let x = reactive(1)
// Define a calculated variable which doubles the values of 'x'
let y = x.map { $0 * 2 }
// Then
print(y) // 2
// Push a new value
x <- 2
// Then
print(y) // 4
mapAll
是 map 的一个特殊版本,它操作在 Try 上的底层 monad,如果您想以某种特殊的方式处理失败。
假设我们想要在除以零时出现错误
let numerator = reactive(4)
let denominator = reactive(1)
// Let's create a tuple
let frac = definedAs {
(numerator*, denominator*)
}.mapAll { (p:Try<(Int, Int)>) -> Try<Int> in
switch p {
case .Success(let box):
let (n, d) = box.value
if d == 0 {
return Try(NSError(domain: "division by zero", code: -0, userInfo: nil))
}
return Try(n/d)
case .Failure(let error): return Try(error)
}
}
// Let's print the errors
frac.onError {
print($0.domain)
}
// And the changes
frac.onChange {
print($0.domain)
}
// If we push a 0 to the denominator
denominator <- 0
// Then a non-zero
denominator <- 2
// The output is the following:
// ----------------------------
// divison by zero
// 2
// Define a reactive stream variable with a starting value of 10
let x = reactive(10)
// Let's pass through values higher than 5
let y = x.filter { $0 > 5 }
// When we push 1
x <- 1
// Value of y will remain the same
print(y*) // 10
// When we push 6
x <- 6
// Value of y will be 6
print(y*) // 6
filterAll
是 map 的一个特殊版本,它操作在 Try 上的底层 monad,如果您想以某种特殊的方式处理失败。
让我们根据 filterAll
实现一下 skipErrors
public func skipErrors() -> Hub<T> {
return filterAll { $0.isSuccess() }
}
// Define a reactive stream variable with a starting value of 1
let x = reactive(1)
// Define a calculated variable which sums the values of 'x'
let sum = x.reduce { $0 + $1 }
// When we push 2
x <- 2
// The sum will be 3
print(sum*) // 3
// When we push 3
x <- 3
// The sum will be 6
print(sum*) // 6
reduceAll
是一种特殊的 map 版本,操作 Try,这是底层 monad,如果想要以某种特殊方式处理失败。
// Define a reactive stream variable with a starting value of 0
let x = reactive(0)
// Let's summarize the values of 'x' and reset the sum to zero if an error arrives
let sum = x.reduceAll { (x, y) in
switch (x, y) {
case (.Success(let a), .Success(let b)): return Try(a.value + b.value)
default: return Try(0)
}
}
// Initially sum is zero
print(sum*) // 0
// When we push 1
x <- 1
// Then it will be 1
print(sum*) // 1
// When we push 2
x <- 2
// Then the sum will be
print(sum*) // 3
// When we push an error
x <- fakeError
// Then it will reset the sum to zero
print(sum*) == 0
// When we push a non-error
x <- 5
// Then it starts again
expect(sum*) == 5
ignore
只是一个对常量的过滤操作
public func ignore(ignorabeValues: T) -> Hub<T> {
return self.filter { $0 != ignorabeValues }
}
// Define a reactive stream variable with a starting value of 1
let x = reactive(1)
// Define a calculated variable which ignores 0
let y = y.ignore(0)
// When we push a 0
x <- 0
// Then nothing changes
print(y*) // 1
// When we push a non-zero
x <- 5
// Then value of y will be 5
print(y*) // 5
throttle
操作符将流中的项放入缓冲区,并在由超时参数指定的时间段到期。如果在时间段到期之前从序列中生成了另一个项,则该新项将替换缓冲区中的旧项,然后重新开始等待。如果在时间段到期之前没有生成其他项,则通过对该序列的任何订阅都可以观察到那个项。
// Define a reactive stream variable with a starting value of 0
let x = reactive(0)
// Define a calculated variable which passes through values after 1 seconds
let y = x.throttle(1.0)
// When we push 1 to the source
x <- 1
// Then it won't change immediately
print(y*) // 0
// If we wait more than one second
// sleep(2)
// Then it's value will be 1
print(y*) // 1
更好的例子,请查看 FlickrExample
使用扩展,在 UIKit 中添加响应式属性相对简单
public extension UILabel {
public var reactiveText: Rx<String> {
// When you read the property it returns a stream variable
get {
return reactiveProperty(forProperty: "text", initValue: self.text!)
}
// It's tricky: we just observers the event stream and if it changes just update the original property
set {
newValue.onChange {
self.text = $0
}
}
}
}
正如您在更复杂的 例子(称为 BasicExample :-))中看到的那样,您还可以添加您自己的便利扩展
extension UITextField {
var isValidEmail: Bool {
return definedAs {
let range = self.reactiveText.value().rangeOfString(emailRegEx, options:.RegularExpressionSearch)
return range != nil
}*
}
var isValidPassword: Bool {
return definedAs {
self.reactiveText*.trim().length > 0
}*
}
}
每当 reactiveText
属性的值发生变化时,它就会自动重新计算 isValidEmail
属性的值。
VinceRP 处于 alpha 阶段,请在生产环境中谨慎使用,并将您的 邮件/推文/github 问题发给我,让我的心情和自豪感更强烈!:-)
添加它,请求它……任何建议、错误报告,以 问题 以及当然 拉取请求 的形式都是受欢迎的!