Skip to content

Commit dadbb19

Browse files
authored
Merge pull request #440 from ReactiveCocoa/binding-target-keypath
BindingTarget + Smart Key Paths.
2 parents 37b0c35 + 5a2d16d commit dadbb19

File tree

3 files changed

+116
-38
lines changed

3 files changed

+116
-38
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# master
22
*Please add new entries at the top.*
33

4+
1. In Swift 3.2 or later, you may create `BindingTarget` for a key path of a specific object. (#440, kudos to @andersio)
5+
46
# 2.0.0-alpha.2
57
1. In Swift 3.2 or later, you can use `map()` with the new Smart Key Paths. (#435, kudos to @sharplet)
68

Sources/UnidirectionalBinding.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,30 @@ public struct BindingTarget<Value>: BindingTargetProvider {
148148
}
149149
self.init(lifetime: lifetime, action: setter)
150150
}
151+
152+
#if swift(>=3.2)
153+
// `Lifetime` is required on these overloads. RAC would provide convenience overloads
154+
// for these with `lifetime(of:)`.
155+
156+
/// Creates a binding target.
157+
///
158+
/// - parameters:
159+
/// - lifetime: The expected lifetime of any bindings towards `self`.
160+
/// - object: The object to consume values.
161+
/// - keyPath: The key path of the object that consumes values.
162+
public init<Object: AnyObject>(lifetime: Lifetime, object: Object, keyPath: ReferenceWritableKeyPath<Object, Value>) {
163+
self.init(lifetime: lifetime) { [weak object] in object?[keyPath: keyPath] = $0 }
164+
}
165+
166+
/// Creates a binding target which consumes values on the specified scheduler.
167+
///
168+
/// - parameters:
169+
/// - scheduler: The scheduler on which the `setter` consumes the values.
170+
/// - lifetime: The expected lifetime of any bindings towards `self`.
171+
/// - object: The object to consume values.
172+
/// - keyPath: The key path of the object that consumes values.
173+
public init<Object: AnyObject>(on scheduler: Scheduler, lifetime: Lifetime, object: Object, keyPath: ReferenceWritableKeyPath<Object, Value>) {
174+
self.init(on: scheduler, lifetime: lifetime) { [weak object] in object?[keyPath: keyPath] = $0 }
175+
}
176+
#endif
151177
}

Tests/ReactiveSwiftTests/UnidirectionalBindingSpec.swift

Lines changed: 88 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,87 +5,135 @@ import Nimble
55
import Quick
66
@testable import ReactiveSwift
77

8+
private class Object {
9+
var value: Int = 0
10+
}
11+
812
class UnidirectionalBindingSpec: QuickSpec {
913
override func spec() {
1014
describe("BindingTarget") {
1115
var token: Lifetime.Token!
1216
var lifetime: Lifetime!
13-
var target: BindingTarget<Int>!
14-
var optionalTarget: BindingTarget<Int?>!
15-
var value: Int?
1617

1718
beforeEach {
1819
token = Lifetime.Token()
1920
lifetime = Lifetime(token)
20-
target = BindingTarget(lifetime: lifetime, action: { value = $0 })
21-
optionalTarget = BindingTarget(lifetime: lifetime, action: { value = $0 })
22-
value = nil
2321
}
2422

25-
describe("non-optional target") {
26-
it("should pass through the lifetime") {
27-
expect(target.lifetime).to(beIdenticalTo(lifetime))
23+
describe("closure binding target") {
24+
var target: BindingTarget<Int>!
25+
var optionalTarget: BindingTarget<Int?>!
26+
var value: Int?
27+
28+
beforeEach {
29+
target = BindingTarget(lifetime: lifetime, action: { value = $0 })
30+
optionalTarget = BindingTarget(lifetime: lifetime, action: { value = $0 })
31+
value = nil
2832
}
2933

30-
it("should trigger the supplied setter") {
31-
expect(value).to(beNil())
34+
describe("non-optional target") {
35+
it("should pass through the lifetime") {
36+
expect(target.lifetime).to(beIdenticalTo(lifetime))
37+
}
3238

33-
target.action(1)
34-
expect(value) == 1
39+
it("should trigger the supplied setter") {
40+
expect(value).to(beNil())
41+
42+
target.action(1)
43+
expect(value) == 1
44+
}
45+
46+
it("should accept bindings from properties") {
47+
expect(value).to(beNil())
48+
49+
let property = MutableProperty(1)
50+
target <~ property
51+
expect(value) == 1
52+
53+
property.value = 2
54+
expect(value) == 2
55+
}
3556
}
3657

37-
it("should accept bindings from properties") {
38-
expect(value).to(beNil())
58+
describe("optional target") {
59+
it("should pass through the lifetime") {
60+
expect(optionalTarget.lifetime).to(beIdenticalTo(lifetime))
61+
}
3962

40-
let property = MutableProperty(1)
41-
target <~ property
42-
expect(value) == 1
63+
it("should trigger the supplied setter") {
64+
expect(value).to(beNil())
4365

44-
property.value = 2
45-
expect(value) == 2
66+
optionalTarget.action(1)
67+
expect(value) == 1
68+
}
69+
70+
it("should accept bindings from properties") {
71+
expect(value).to(beNil())
72+
73+
let property = MutableProperty(1)
74+
optionalTarget <~ property
75+
expect(value) == 1
76+
77+
property.value = 2
78+
expect(value) == 2
79+
}
4680
}
4781
}
4882

49-
describe("optional target") {
83+
#if swift(>=3.2)
84+
describe("key path binding target") {
85+
var target: BindingTarget<Int>!
86+
var object: Object!
87+
88+
beforeEach {
89+
object = Object()
90+
target = BindingTarget(lifetime: lifetime, object: object, keyPath: \.value)
91+
}
92+
5093
it("should pass through the lifetime") {
51-
expect(optionalTarget.lifetime).to(beIdenticalTo(lifetime))
94+
expect(target.lifetime).to(beIdenticalTo(lifetime))
5295
}
5396

5497
it("should trigger the supplied setter") {
55-
expect(value).to(beNil())
98+
expect(object.value) == 0
5699

57-
optionalTarget.action(1)
58-
expect(value) == 1
100+
target.action(1)
101+
expect(object.value) == 1
59102
}
60103

61104
it("should accept bindings from properties") {
62-
expect(value).to(beNil())
105+
expect(object.value) == 0
63106

64107
let property = MutableProperty(1)
65-
optionalTarget <~ property
66-
expect(value) == 1
108+
target <~ property
109+
expect(object.value) == 1
67110

68111
property.value = 2
69-
expect(value) == 2
112+
expect(object.value) == 2
70113
}
71114
}
115+
#endif
72116

73117
it("should not deadlock on the same queue") {
74-
target = BindingTarget(on: UIScheduler(),
75-
lifetime: lifetime,
76-
action: { value = $0 })
118+
var value: Int?
119+
120+
let target = BindingTarget(on: UIScheduler(),
121+
lifetime: lifetime,
122+
action: { value = $0 })
77123

78124
let property = MutableProperty(1)
79125
target <~ property
80126
expect(value) == 1
81127
}
82128

83129
it("should not deadlock on the main thread even if the context was switched to a different queue") {
130+
var value: Int?
131+
84132
let queue = DispatchQueue(label: #file)
85133

86-
target = BindingTarget(on: UIScheduler(),
87-
lifetime: lifetime,
88-
action: { value = $0 })
134+
let target = BindingTarget(on: UIScheduler(),
135+
lifetime: lifetime,
136+
action: { value = $0 })
89137

90138
let property = MutableProperty(1)
91139

@@ -97,6 +145,8 @@ class UnidirectionalBindingSpec: QuickSpec {
97145
}
98146

99147
it("should not deadlock even if the value is originated from the same queue indirectly") {
148+
var value: Int?
149+
100150
let key = DispatchSpecificKey<Void>()
101151
DispatchQueue.main.setSpecific(key: key, value: ())
102152

@@ -107,9 +157,9 @@ class UnidirectionalBindingSpec: QuickSpec {
107157
mainQueueCounter.modify { $0 += DispatchQueue.getSpecific(key: key) != nil ? 1 : 0 }
108158
}
109159

110-
target = BindingTarget(on: UIScheduler(),
111-
lifetime: lifetime,
112-
action: setter)
160+
let target = BindingTarget(on: UIScheduler(),
161+
lifetime: lifetime,
162+
action: setter)
113163

114164
let scheduler: QueueScheduler
115165
if #available(OSX 10.10, *) {

0 commit comments

Comments
 (0)