VinceRP 0.2.5

VinceRP 0.2.5

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布上次发布2015年12月
SPM支持 SPM

Viktor Belenyesi 维护。



VinceRP 0.2.5

VinceRP

适用于 Swift 的高效、易扩展的反应式框架。

Bitrise codecov.io

入门

该框架目前支持 iOS 和 Mac。

兼容性

  • Swift 2.x
  • iOS 8.3 及以上版本
  • OS X 10.10 及以上版本

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)"

操作符

not

它适用于 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

skipErrors

// 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

foreach

// 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]

map

// 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

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

filter

// 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

filterAll 是 map 的一个特殊版本,它操作在 Try 上的底层 monad,如果您想以某种特殊的方式处理失败。

让我们根据 filterAll 实现一下 skipErrors

public func skipErrors() -> Hub<T> {
    return filterAll { $0.isSuccess() }
}

reduce

// 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

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

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

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 问题发给我,让我的心情和自豪感更强烈!:-)

您是否缺少某些功能?

添加它,请求它……任何建议、错误报告,以 问题 以及当然 拉取请求 的形式都是受欢迎的!

Vince 是谁?

Vince

参考资料

许可证

VinceRP 在 MIT 许可证下发布。