VinceRP
Easy to use, easy to extend reactive framework for Swift.
Install / Use
/learn @bvic23/VinceRPREADME

Easy to use, easy to extend reactive framework for Swift.
#Getting Started
The framework supports iOS & Mac for now.
##Compatibility
- Swift 2.x
- iOS 8.3 and up
- OS X 10.10 and up
tvOS and watchOS not yet tested/supported.
##Install
###Carthage
-
Add VinceRP to your Cartfile:
github "bvic23/VinceRP" -
Download and install the latest Carthage from binary. (Homebrew is still on 0.8 which is outdated.)
-
Set VinceRP up as a dependency to your project:
carthage update --platform iOS, Mac
###CocoaPods
#Easy to use
Let's see a basic example:
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
Note that - thanks to Swift's type inference - it figures out the type of the sources and the sum as well. So the following won't compile:
import VinceRP
let s = reactive(1)
let s2 = definedAs{ "s*2 = \(s.value() * 2)" }
let s3 = definedAs{ s2* + s* }
You might see a weird error message but it's all about the missing <string> + <int> operator.
##Side effects
Of course you can have side effects:
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
If you don't want to count the initialization:
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
##Errors
If you're interested in errors:
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)"
#Operators
###not
It is applicable for Bool streams and negates the values.
// 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
###distinct
// Define a reactive stream variable with '1' as initial value
let a = reactive(1)
// Define a calculated variable with 'distinct' modifier which ensures it won’t react unless the value has actually changed
let b = a.distinct()
// Let's count the changes of b
let counter = 0
b.onChange(skipInitial: true) { _ in counter++ }
// It's value is 1
print(c) // 0
// If we push an equal (same) value
a <- 1
// c is still 0
print(c) // 0
// If we push a different value
a <- 2
// c becomes 1
print(c) // 1
###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 is a special version of map which operates on Try<T>, the underlying monad if you want to handle Failures in some special way.
Let's say we would like to have an error if division by zero is happening:
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 is a special version of map which operates on Try<T>, the underlying monad if you want to handle Failures in some special way.
Let's implement skipErrors in terms of filterAll:
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 is a special version of map which operates on Try<T>, the underlying monad if you want to handle Failures in some special way.
// 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 is simply a filter against a constant value:
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 operator buffers an item from the stream and wait for the time span specified by the timeout parameter to expire. If another item is produced from the sequence before the time span expires, Then that item replaces the old item in the buffer and the wait starts over. If the due time does expire before anoth
Related Skills
node-connect
349.0kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
109.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
349.0kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
349.0kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
