Skip to content

Commit 06cdc46

Browse files
authored
Merge pull request #5 from timsearle/mutable
Added concept of a MutableOutput for better semantic protection and guidance for use
2 parents bf3767f + 9ecd246 commit 06cdc46

File tree

4 files changed

+72
-50
lines changed

4 files changed

+72
-50
lines changed

Sources/Bind/Output.swift

+21-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class Output<Value>: Unbindable {
1717
self.printer = printer
1818
}
1919

20-
public func update(withValue value: Value) {
20+
fileprivate func update(withValue value: Value) {
2121
if let identifier = debugIdentifier {
2222
printer.print("---")
2323
printer.print("Will update value for \(identifier) (\(type(of: self))) to \(value)")
@@ -91,12 +91,32 @@ public class Output<Value>: Unbindable {
9191
public func unbind(for subscription: Subscription) {
9292
observers[subscription] = nil
9393
}
94+
95+
/**
96+
`asMutable` converts an Output into a MutableOutput of the same Value allowing its value to be updated a later date
97+
- Returns: A mutable version of the receiver
98+
*/
99+
public func asMutable() -> MutableOutput<Value> {
100+
let mutableOutput = MutableOutput<Value>()
101+
102+
bind { value in
103+
mutableOutput.update(withValue: value)
104+
}
105+
106+
return mutableOutput
107+
}
94108
}
95109

96110
public protocol Printable {
97111
func print(_ value: String)
98112
}
99113

114+
public class MutableOutput<Value>: Output<Value> {
115+
public override func update(withValue value: Value) {
116+
super.update(withValue: value)
117+
}
118+
}
119+
100120
// MARK: - Functional extensions
101121
public extension Output {
102122
static func combine(outputs: [Output<Value>]) -> Output<[Value]> {

Sources/Bind/Relay.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
public final class Relay: Output<Void> {
1+
public final class Relay: MutableOutput<Void> {
22
public func fire() {
33
update(withValue: ())
44
}

Tests/BindTests/BindableTestsUIKit.swift

+15-15
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ final class BindableTestsUIKit: XCTestCase {
1010
label.textColor = .black
1111
let attributedLabel = UILabel()
1212

13-
let textOutput = Output<String>()
14-
let textColorOutput = Output<UIColor>()
15-
let attributedTextOutput = Output<NSAttributedString?>()
13+
let textOutput = MutableOutput<String>()
14+
let textColorOutput = MutableOutput<UIColor>()
15+
let attributedTextOutput = MutableOutput<NSAttributedString?>()
1616

1717
textOutput.bind(to: label.binding.text)
1818
textColorOutput.bind(to: label.binding.textColor)
@@ -34,12 +34,12 @@ final class BindableTestsUIKit: XCTestCase {
3434
func testViewBools() {
3535
let view = UIView()
3636

37-
let hiddenOutput = Output<Bool>()
38-
let visibleOutput = Output<Bool>()
39-
let visibleAlpha = Output<Bool>()
40-
let visibleAlphaAnimated = Output<Bool>()
41-
let userInteractionEnabledOutput = Output<Bool>()
42-
let constraintsActive = Output<Bool>()
37+
let hiddenOutput = MutableOutput<Bool>()
38+
let visibleOutput = MutableOutput<Bool>()
39+
let visibleAlpha = MutableOutput<Bool>()
40+
let visibleAlphaAnimated = MutableOutput<Bool>()
41+
let userInteractionEnabledOutput = MutableOutput<Bool>()
42+
let constraintsActive = MutableOutput<Bool>()
4343

4444
hiddenOutput.bind(to: view.binding.isHidden)
4545
visibleOutput.bind(to: view.binding.isVisible)
@@ -78,9 +78,9 @@ final class BindableTestsUIKit: XCTestCase {
7878
func testViewUIColors() {
7979
let view = UIView()
8080

81-
let backgroundColorOutput = Output<UIColor>()
82-
let borderColorOutput = Output<UIColor>()
83-
let tintColorOutput = Output<UIColor>()
81+
let backgroundColorOutput = MutableOutput<UIColor>()
82+
let borderColorOutput = MutableOutput<UIColor>()
83+
let tintColorOutput = MutableOutput<UIColor>()
8484

8585
backgroundColorOutput.bind(to: view.binding.backgroundColor)
8686
borderColorOutput.bind(to: view.binding.borderColor)
@@ -99,8 +99,8 @@ final class BindableTestsUIKit: XCTestCase {
9999
func testViewCGFloat() {
100100
let view = UIView()
101101

102-
let borderWidthOutput = Output<CGFloat>()
103-
let cornerRadiusOutput = Output<CGFloat>()
102+
let borderWidthOutput = MutableOutput<CGFloat>()
103+
let cornerRadiusOutput = MutableOutput<CGFloat>()
104104

105105
borderWidthOutput.bind(to: view.binding.borderWidth)
106106
cornerRadiusOutput.bind(to: view.binding.cornerRadius)
@@ -115,7 +115,7 @@ final class BindableTestsUIKit: XCTestCase {
115115
func testViewString() {
116116
let view = UIView()
117117

118-
let accessibilityOutput = Output<String>()
118+
let accessibilityOutput = MutableOutput<String>()
119119

120120
accessibilityOutput.bind(to: view.binding.accessibilityIdentifier)
121121

Tests/BindTests/OutputTests.swift

+35-33
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ import XCTest
33

44
final class OutputTests: XCTestCase {
55
func testInitialNoValue() {
6-
let output = Output<String>()
6+
let output = MutableOutput<String>()
77
output.bind { _ in
88
XCTFail("should not be called as Output has no value set")
99
}
1010
}
1111

1212
func testInitialValue() {
13-
let output = Output<String>(value: "Test")
13+
let output = MutableOutput<String>(value: "Test")
1414
var closureCalled: Bool = false
1515
output.debug(identifier: "123").bind { value in
1616
XCTAssertEqual("Test", value)
@@ -22,7 +22,7 @@ final class OutputTests: XCTestCase {
2222
func testBinderIsCalled() {
2323
let testObject = BindableMock()
2424

25-
let output = Output<String>()
25+
let output = MutableOutput<String>()
2626
output.bind(to: testObject.binding.text)
2727

2828
XCTAssertNil(testObject.text)
@@ -36,7 +36,7 @@ final class OutputTests: XCTestCase {
3636
let testObjectOne = BindableMock()
3737
let testObjectTwo = BindableMock()
3838

39-
let output = Output<String>()
39+
let output = MutableOutput<String>()
4040
output.bind(to: [testObjectOne.binding.text,
4141
testObjectTwo.binding.text])
4242

@@ -52,7 +52,7 @@ final class OutputTests: XCTestCase {
5252
func testUnbind() {
5353
let testObject = BindableMock()
5454

55-
let output = Output<String>()
55+
let output = MutableOutput<String>()
5656
let subscription = output.bind(to: testObject.binding.text)
5757

5858
XCTAssertNil(testObject.text)
@@ -69,13 +69,13 @@ final class OutputTests: XCTestCase {
6969
}
7070

7171
func testCombine() {
72-
let output1 = Output<Bool>()
73-
let output2 = Output<Bool>()
72+
let output1 = MutableOutput<Bool>()
73+
let output2 = MutableOutput<Bool>()
7474

7575
var outputValue1: Bool?
7676
var outputValue2: Bool?
7777

78-
Output<Bool>.combine(output1, output2).bind { value1, value2 in
78+
MutableOutput<Bool>.combine(output1, output2).bind { value1, value2 in
7979
outputValue1 = value1
8080
outputValue2 = value2
8181
}
@@ -100,15 +100,15 @@ final class OutputTests: XCTestCase {
100100
}
101101

102102
func testCombineArray() {
103-
let output1 = Output<Bool>()
104-
let output2 = Output<Bool>()
105-
let output3 = Output<Bool>()
103+
let output1 = MutableOutput<Bool>()
104+
let output2 = MutableOutput<Bool>()
105+
let output3 = MutableOutput<Bool>()
106106

107107
var outputValue1: Bool?
108108
var outputValue2: Bool?
109109
var outputValue3: Bool?
110110

111-
Output<Bool>.combine(outputs: [output1, output2, output3]).bind { valuesArray in
111+
MutableOutput<Bool>.combine(outputs: [output1, output2, output3]).bind { valuesArray in
112112
outputValue1 = valuesArray[0]
113113
outputValue2 = valuesArray[1]
114114
outputValue3 = valuesArray[2]
@@ -144,13 +144,13 @@ final class OutputTests: XCTestCase {
144144
}
145145

146146
func testCombineTwoTypes() {
147-
let output1 = Output<Bool>()
148-
let output2 = Output<String>()
147+
let output1 = MutableOutput<Bool>()
148+
let output2 = MutableOutput<String>()
149149

150150
var outputValue1: Bool?
151151
var outputValue2: String?
152152

153-
Output<Bool>
153+
MutableOutput<Bool>
154154
.combine(output1, output2)
155155
.bind { value1, value2 in
156156
outputValue1 = value1
@@ -183,9 +183,9 @@ final class OutputTests: XCTestCase {
183183
case two
184184
}
185185

186-
let value = Output<TestEnum>()
186+
let value = MutableOutput<TestEnum>()
187187

188-
let mappedValue: Output<String> =
188+
let mappedValue: MutableOutput<String> =
189189
value
190190
.map { type in
191191
switch type {
@@ -195,6 +195,7 @@ final class OutputTests: XCTestCase {
195195
return "two"
196196
}
197197
}
198+
.asMutable()
198199

199200
value.update(withValue: .one)
200201
XCTAssertEqual(mappedValue.latest, "one")
@@ -213,9 +214,9 @@ final class OutputTests: XCTestCase {
213214
case two
214215
}
215216

216-
let value = Output<TestEnum>()
217+
let value = MutableOutput<TestEnum>()
217218

218-
let mappedValue: Output<String> =
219+
let mappedValue: MutableOutput<String> =
219220
value
220221
.flatMap { type in
221222
switch type {
@@ -225,6 +226,7 @@ final class OutputTests: XCTestCase {
225226
return Output(value: "two")
226227
}
227228
}
229+
.asMutable()
228230

229231
value.update(withValue: .one)
230232
XCTAssertEqual(mappedValue.latest, "one")
@@ -237,7 +239,7 @@ final class OutputTests: XCTestCase {
237239
}
238240

239241
func testFilter() {
240-
let value = Output<String>()
242+
let value = MutableOutput<String>()
241243

242244
let filteredValue = value
243245
.filter { string in
@@ -255,8 +257,8 @@ final class OutputTests: XCTestCase {
255257
}
256258

257259
func testMerge() {
258-
let output1 = Output<Int>()
259-
let output2 = Output<Int>()
260+
let output1 = MutableOutput<Int>()
261+
let output2 = MutableOutput<Int>()
260262

261263
let merge = Output.merge(output1, output2)
262264

@@ -280,7 +282,7 @@ final class OutputTests: XCTestCase {
280282
var currentString: String = ""
281283
}
282284

283-
let initial = Output<Int>()
285+
let initial = MutableOutput<Int>()
284286

285287
let reduced = initial
286288
.reduce(initial: TestObject()) { current, number -> TestObject in
@@ -298,7 +300,7 @@ final class OutputTests: XCTestCase {
298300
}
299301

300302
func testReduceValueType() {
301-
let initial = Output<Int>()
303+
let initial = MutableOutput<Int>()
302304

303305
let reduced = initial.reduce(initial: 0, nextPartialResult: +)
304306

@@ -310,16 +312,16 @@ final class OutputTests: XCTestCase {
310312
}
311313

312314
func testInitialValueFunctionChain() {
313-
let initial = Output<Int>().initial(10)
315+
let initial = MutableOutput<Int>().initial(10)
314316
XCTAssertEqual(initial.latest, 10)
315317

316-
let alreadyPopulated = Output<Int>(value: 5).initial(10)
318+
let alreadyPopulated = MutableOutput<Int>(value: 5).initial(10)
317319
XCTAssertEqual(alreadyPopulated.latest, 5)
318320

319-
let left = Output(value: 3)
320-
let right = Output<Int>()
321+
let left = MutableOutput(value: 3)
322+
let right = MutableOutput<Int>()
321323

322-
let combined = Output
324+
let combined = MutableOutput
323325
.combine(left, right)
324326
.map(+)
325327
.initial(20)
@@ -335,7 +337,7 @@ final class OutputTests: XCTestCase {
335337

336338
func testDebug() {
337339
let printer = PrinterMock()
338-
let output1 = Output<Bool>(printer: printer)
340+
let output1 = MutableOutput<Bool>(printer: printer)
339341

340342
XCTAssertTrue(printer.printValues.isEmpty)
341343

@@ -345,16 +347,16 @@ final class OutputTests: XCTestCase {
345347

346348
XCTAssertEqual(printer.printValues.count, 3)
347349
XCTAssertEqual(printer.printValues[0], "---")
348-
XCTAssertEqual(printer.printValues[1], "Binding 123 (Output<Bool>) to (Function)")
350+
XCTAssertEqual(printer.printValues[1], "Binding 123 (MutableOutput<Bool>) to (Function)")
349351
XCTAssertEqual(printer.printValues[2], "To bindings: [Bind.Subscription: (Function)]")
350352

351353
output1.update(withValue: false)
352354

353355
XCTAssertEqual(printer.printValues.count, 8)
354356
XCTAssertEqual(printer.printValues[3], "---")
355-
XCTAssertEqual(printer.printValues[4], "Will update value for 123 (Output<Bool>) to false")
357+
XCTAssertEqual(printer.printValues[4], "Will update value for 123 (MutableOutput<Bool>) to false")
356358
XCTAssertEqual(printer.printValues[5], "To bindings: [Bind.Subscription: (Function)]")
357-
XCTAssertEqual(printer.printValues[6], "Did update value for 123 (Output<Bool>) to false")
359+
XCTAssertEqual(printer.printValues[6], "Did update value for 123 (MutableOutput<Bool>) to false")
358360
XCTAssertEqual(printer.printValues[7], "To bindings: [Bind.Subscription: (Function)]")
359361
}
360362
}

0 commit comments

Comments
 (0)