Skip to content

Commit d01a25e

Browse files
authored
Merge pull request #165 from ReactiveCocoa/binding-operator-fix
Fix broken non-optional to optional bindings.
2 parents c9cb48b + 29d6133 commit d01a25e

File tree

2 files changed

+87
-34
lines changed

2 files changed

+87
-34
lines changed

Sources/UnidirectionalBinding.swift

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@ public func <~
8787
// Alter the semantics of `BindingTarget` to not require it to be retained.
8888
// This is done here--and not in a separate function--so that all variants
8989
// of `<~` can get this behavior.
90-
let observer: Observer<Source.Value, NoError>
91-
if let target = target as? BindingTarget<Source.Value> {
90+
let observer: Observer<Target.Value, NoError>
91+
if let target = target as? BindingTarget<Target.Value> {
9292
observer = Observer(value: { [setter = target.setter] in setter($0) })
9393
} else {
9494
observer = Observer(value: { [weak target] in target?.consume($0) })
@@ -136,10 +136,21 @@ public func <~
136136
(target: Target, source: Source) -> Disposable?
137137
where Target.Value: OptionalProtocol, Source.Value == Target.Value.Wrapped, Source.Error == NoError
138138
{
139-
let newTarget = BindingTarget<Source.Value>(lifetime: target.lifetime) { [weak target] value in
140-
target?.consume(Target.Value(reconstructing: value))
139+
// Alter the semantics of `BindingTarget` to not require it to be retained.
140+
// This is done here--and not in a separate function--so that all variants
141+
// of `<~` can get this behavior.
142+
let observer: Observer<Source.Value, NoError>
143+
if let target = target as? BindingTarget<Target.Value> {
144+
observer = Observer(value: { [setter = target.setter] in setter(Target.Value(reconstructing: $0)) })
145+
} else {
146+
observer = Observer(value: { [weak target] in target?.consume(Target.Value(reconstructing: $0)) })
141147
}
142-
return newTarget <~ source
148+
149+
let disposable = source.observe(observer)
150+
if let disposable = disposable {
151+
target.lifetime.ended.observeCompleted { disposable.dispose() }
152+
}
153+
return disposable
143154
}
144155

145156
/// A binding target that can be used with the `<~` operator.

Tests/ReactiveSwiftTests/UnidirectionalBindingSpec.swift

Lines changed: 71 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,49 +11,91 @@ class UnidirectionalBindingSpec: QuickSpec {
1111
var token: Lifetime.Token!
1212
var lifetime: Lifetime!
1313
var target: BindingTarget<Int>!
14-
var value: Int!
14+
var optionalTarget: BindingTarget<Int?>!
15+
var value: Int?
1516

1617
beforeEach {
1718
token = Lifetime.Token()
1819
lifetime = Lifetime(token)
1920
target = BindingTarget(lifetime: lifetime, setter: { value = $0 })
21+
optionalTarget = BindingTarget(lifetime: lifetime, setter: { value = $0 })
2022
value = nil
2123
}
2224

23-
it("should pass through the lifetime") {
24-
expect(target.lifetime).to(beIdenticalTo(lifetime))
25-
}
26-
27-
it("should stay bound after deallocation") {
28-
weak var weakTarget = target
29-
30-
let property = MutableProperty(1)
31-
target <~ property
32-
expect(value) == 1
33-
34-
target = nil
35-
36-
property.value = 2
37-
expect(value) == 2
38-
expect(weakTarget).to(beNil())
39-
}
25+
describe("non-optional target") {
26+
it("should pass through the lifetime") {
27+
expect(target.lifetime).to(beIdenticalTo(lifetime))
28+
}
4029

41-
it("should trigger the supplied setter") {
42-
expect(value).to(beNil())
30+
it("should stay bound after deallocation") {
31+
weak var weakTarget = target
4332

44-
target.consume(1)
45-
expect(value) == 1
33+
let property = MutableProperty(1)
34+
target <~ property
35+
expect(value) == 1
36+
37+
target = nil
38+
39+
property.value = 2
40+
expect(value) == 2
41+
expect(weakTarget).to(beNil())
42+
}
43+
44+
it("should trigger the supplied setter") {
45+
expect(value).to(beNil())
46+
47+
target.consume(1)
48+
expect(value) == 1
49+
}
50+
51+
it("should accept bindings from properties") {
52+
expect(value).to(beNil())
53+
54+
let property = MutableProperty(1)
55+
target <~ property
56+
expect(value) == 1
57+
58+
property.value = 2
59+
expect(value) == 2
60+
}
4661
}
4762

48-
it("should accept bindings from properties") {
49-
expect(value).to(beNil())
63+
describe("optional target") {
64+
it("should pass through the lifetime") {
65+
expect(optionalTarget.lifetime).to(beIdenticalTo(lifetime))
66+
}
5067

51-
let property = MutableProperty(1)
52-
target <~ property
53-
expect(value) == 1
68+
it("should stay bound after deallocation") {
69+
weak var weakTarget = optionalTarget
5470

55-
property.value = 2
56-
expect(value) == 2
71+
let property = MutableProperty(1)
72+
optionalTarget <~ property
73+
expect(value) == 1
74+
75+
optionalTarget = nil
76+
77+
property.value = 2
78+
expect(value) == 2
79+
expect(weakTarget).to(beNil())
80+
}
81+
82+
it("should trigger the supplied setter") {
83+
expect(value).to(beNil())
84+
85+
optionalTarget.consume(1)
86+
expect(value) == 1
87+
}
88+
89+
it("should accept bindings from properties") {
90+
expect(value).to(beNil())
91+
92+
let property = MutableProperty(1)
93+
optionalTarget <~ property
94+
expect(value) == 1
95+
96+
property.value = 2
97+
expect(value) == 2
98+
}
5799
}
58100

59101
it("should not deadlock on the same queue") {

0 commit comments

Comments
 (0)