Skip to content

Commit

Permalink
Key–path bindings.
Browse files Browse the repository at this point in the history
  • Loading branch information
srdanrasic committed Oct 27, 2017
1 parent 4fc13a7 commit 72e5cf7
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 7 deletions.
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -827,21 +827,27 @@ First of all, notice `@discardableResult` annotation. It is there because we can

#### Binding to a property

Ok, that was binding to an object, but what about binding to a property? Would it not be easier that instead of doing
Given a string signal `name`, we know that we can bind it to a label by doing

```swift
name.bind(to: label) { label, name in
label.text = name
}
```

we can do something like:
but would it not be great if we could make it a one-liner? With Swift 4 key paths we can! Just do

```swift
name.bind(to: label.reactive.text)
name.bind(to: label, keyPath: \.text)
```

And, of course, we can - by using the [Bond framework](https://github.com/ReactiveKit/Bond)!
where the target is the same target as in previous example and `keyPath` is a key path to the property that should be updated with each new element sent on the signal!

If you opt-in for a [Bond framework](https://github.com/ReactiveKit/Bond), things get even simpler:

```swift
name.bind(to: label.reactive.text)
```

Bond provides a type called `Bond` that acts as a binding target that we can use to make reactive extensions for various properties. Check out its documentation for more info.

Expand Down
4 changes: 2 additions & 2 deletions ReactiveKit.podspec
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Pod::Spec.new do |s|
s.name = "ReactiveKit"
s.version = "3.7.5"
s.version = "3.8.0"
s.summary = "A Swift Reactive Programming Framework"
s.description = "ReactiveKit is a Swift framework for reactive and functional reactive programming."
s.homepage = "https://github.com/ReactiveKit/ReactiveKit"
s.license = 'MIT'
s.author = { "Srdan Rasic" => "srdan.rasic@gmail.com" }
s.source = { :git => "https://github.com/ReactiveKit/ReactiveKit.git", :tag => "v3.7.5" }
s.source = { :git => "https://github.com/ReactiveKit/ReactiveKit.git", :tag => "v3.8.0" }

s.ios.deployment_target = '8.0'
s.osx.deployment_target = '10.9'
Expand Down
2 changes: 1 addition & 1 deletion ReactiveKit/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>3.7.5</string>
<string>3.8.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
Expand Down
39 changes: 39 additions & 0 deletions Sources/Bindable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,45 @@ extension SignalProtocol where Error == NoError {
}
}
}

/// Bind the receiver to target's property specified by the key path. The property is
/// updated whenever the signal emits `next` event.
///
/// Binding lives until either the signal completes or the target is deallocated.
/// That means that the returned disposable can be safely ignored.
///
/// - Parameters:
/// - target: A binding target. Conforms to `Deallocatable` so it can inform the binding
/// when it gets deallocated. Upon target deallocation, the binding gets automatically disposed.
/// - keyPath: A key path to the property that will be updated with each sent element.
/// - Returns: A disposable that can cancel the binding.
@discardableResult
public func bind<Target: Deallocatable>(to target: Target, keyPath: ReferenceWritableKeyPath<Target, Element>) -> Disposable where Target: BindingExecutionContextProvider
{
return bind(to: target) { (target, element) in
target[keyPath: keyPath] = element
}
}

/// Bind the receiver to target's property specified by the key path. The property is
/// updated whenever the signal emits `next` event.
///
/// Binding lives until either the signal completes or the target is deallocated.
/// That means that the returned disposable can be safely ignored.
///
/// - Parameters:
/// - target: A binding target. Conforms to `Deallocatable` so it can inform the binding
/// when it gets deallocated. Upon target deallocation, the binding gets automatically disposed.
/// - keyPath: A key path to the property that will be updated with each sent element.
/// - context: An execution context on which to execute the setter.
/// - Returns: A disposable that can cancel the binding.
@discardableResult
public func bind<Target: Deallocatable>(to target: Target, keyPath: ReferenceWritableKeyPath<Target, Element>, context: ExecutionContext) -> Disposable
{
return bind(to: target, context: context) { (target, element) in
target[keyPath: keyPath] = element
}
}
}

extension SignalProtocol where Error == NoError, Element == Void {
Expand Down
20 changes: 20 additions & 0 deletions Tests/ReactiveKitTests/SignalTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -555,4 +555,24 @@ class SignalTests: XCTestCase {

XCTAssertEqual(bob.numberOfRuns, 2)
}

func testBindTo() {

class User: NSObject, BindingExecutionContextProvider {

var age: Int = 0

var bindingExecutionContext: ExecutionContext {
return .immediate
}
}

let user = User()

SafeSignal.just(20).bind(to: user) { (object, value) in object.age = value }
XCTAssertEqual(user.age, 20)

SafeSignal.just(30).bind(to: user, keyPath: \.age)
XCTAssertEqual(user.age, 30)
}
}

0 comments on commit 72e5cf7

Please sign in to comment.