From 772c5fd195493e5efdabd736150efea880723253 Mon Sep 17 00:00:00 2001 From: Quickthyme Date: Tue, 12 Nov 2019 16:59:32 -0600 Subject: [PATCH 1/2] repeater timing Signed-off-by: Quickthyme --- Sources/QLoop/QLAnchor+ConvenienceInit.swift | 36 ++++++++++-- Sources/QLoop/QLAnchor.swift | 42 +++++++++---- Tests/QLoopTests/QLAnchorTests.swift | 62 ++++++++++++++++++-- docs/changelog.md | 6 ++ docs/reference/QLAnchor.md | 10 ++-- 5 files changed, 128 insertions(+), 28 deletions(-) diff --git a/Sources/QLoop/QLAnchor+ConvenienceInit.swift b/Sources/QLoop/QLAnchor+ConvenienceInit.swift index 942f091..ac22d19 100644 --- a/Sources/QLoop/QLAnchor+ConvenienceInit.swift +++ b/Sources/QLoop/QLAnchor+ConvenienceInit.swift @@ -12,22 +12,46 @@ public extension QLAnchor { onError: QLAnchor.emptyErr) } - convenience init(repeaters: QLAnchor...) { + convenience init(earlyRepeaters: QLAnchor...) { self.init(echoFilter: QLAnchor.DefaultEchoFilter, - repeaters: repeaters) + earlyRepeaters: earlyRepeaters, + lateRepeaters: []) + } + + convenience init(lateRepeaters: QLAnchor...) { + self.init(echoFilter: QLAnchor.DefaultEchoFilter, + earlyRepeaters: [], + lateRepeaters: lateRepeaters) + } + + convenience init(earlyRepeaters: [QLAnchor], + lateRepeaters: [QLAnchor]) { + self.init(echoFilter: QLAnchor.DefaultEchoFilter, + earlyRepeaters: earlyRepeaters, + lateRepeaters: lateRepeaters) + } + + convenience init(echoFilter: @escaping EchoFilter, + earlyRepeaters: QLAnchor...) { + self.init(echoFilter: echoFilter, + earlyRepeaters: earlyRepeaters, + lateRepeaters: []) } convenience init(echoFilter: @escaping EchoFilter, - repeaters: QLAnchor...) { + lateRepeaters: QLAnchor...) { self.init(echoFilter: echoFilter, - repeaters: repeaters) + earlyRepeaters: [], + lateRepeaters: lateRepeaters) } convenience init(echoFilter: @escaping EchoFilter, - repeaters: [QLAnchor]) { + earlyRepeaters: [QLAnchor], + lateRepeaters: [QLAnchor]) { self.init(onChange: QLAnchor.emptyIn, onError: QLAnchor.emptyErr) - self.repeaters = repeaters + self.addRepeaters(earlyRepeaters, timing: .early) + self.addRepeaters(lateRepeaters, timing: .late) self.echoFilter = echoFilter } } diff --git a/Sources/QLoop/QLAnchor.swift b/Sources/QLoop/QLAnchor.swift index 9a81fe8..155d361 100644 --- a/Sources/QLoop/QLAnchor.swift +++ b/Sources/QLoop/QLAnchor.swift @@ -11,19 +11,29 @@ public final class QLAnchor: AnyAnchor { public typealias EchoFilter = (Input?, QLAnchor) -> (Bool) internal static var DefaultEchoFilter: EchoFilter { return { _, _ in return true } } + public enum Timing { + case early, late + } + internal final class Repeater { - weak var anchor: QLAnchor? - init(_ anchor: QLAnchor) { + + weak var anchor: QLAnchor! + + let timing: Timing + + init(_ anchor: QLAnchor, timing: Timing) { + self.timing = timing self.anchor = anchor } - func echo(value: Input?, filter: EchoFilter) { - if let repeater = self.anchor, - filter(value, repeater) { - repeater.value = value + + func echo(value: Input?, filter: EchoFilter, timing: Timing) { + if filter(value, anchor) { + anchor.value = value } } + func echo(error: Error) { - anchor?.error = error + anchor.error = error } } @@ -42,9 +52,13 @@ public final class QLAnchor: AnyAnchor { public var inputSegment: AnySegment? - public var repeaters: [QLAnchor] { - get { return _repeaters.compactMap { $0.anchor } } - set { self._repeaters = newValue.map { Repeater($0) } } + public func addRepeaters(_ repeaters: [QLAnchor], timing: Timing) { + let repeaters = repeaters.map { Repeater($0, timing: timing) } + self._repeaters.append(contentsOf: repeaters) + } + + public func getRepeaters(timing: Timing) -> [QLAnchor] { + return _repeaters.compactMap { ($0.timing == timing) ? $0.anchor : nil } } internal var _repeaters: [Repeater] = [] @@ -64,8 +78,9 @@ public final class QLAnchor: AnyAnchor { if let err = getReroutableError(newValue) { self.error = err } else { + echo(value: newValue, timing: .early) dispatch(value: newValue) - echo(value: newValue) + echo(value: newValue, timing: .late) } if (QLCommon.Config.Anchor.releaseValues) { @@ -113,9 +128,10 @@ public final class QLAnchor: AnyAnchor { } } - private func echo(value: Input?) { + private func echo(value: Input?, timing: Timing) { for repeater in _repeaters { - repeater.echo(value: value, filter: echoFilter) + guard repeater.timing == timing else { continue } + repeater.echo(value: value, filter: echoFilter, timing: timing) } } diff --git a/Tests/QLoopTests/QLAnchorTests.swift b/Tests/QLoopTests/QLAnchorTests.swift index 57297ba..c6ab119 100644 --- a/Tests/QLoopTests/QLAnchorTests.swift +++ b/Tests/QLoopTests/QLAnchorTests.swift @@ -81,7 +81,7 @@ final class QLAnchorTests: XCTestCase { XCTAssert((receivedError as? QLCommon.Error) == QLCommon.Error.ThrownButNotSet) } - func test_given_it_has_repeaters_with_default_filter_when_input_set_then_it_echoes_to_them_as_well() { + func test_given_it_has_early_repeaters_with_default_filter_when_input_set_then_it_echoes_to_them_as_well() { var receivedVal0: Int = -1 var receivedVal1: Int = -1 var receivedVal2: Int = -1 @@ -90,7 +90,30 @@ final class QLAnchorTests: XCTestCase { let expectRepeater2 = expectation(description: "should echo value to repeater2") let repeater1 = QLAnchor(onChange: { receivedVal1 = $0!; expectRepeater1.fulfill() }) let repeater2 = QLAnchor(onChange: { receivedVal2 = $0!; expectRepeater2.fulfill() }) - let subject = QLAnchor(repeaters: repeater1, repeater2) + let subject = QLAnchor(earlyRepeaters: repeater1, repeater2) + XCTAssertEqual(subject.getRepeaters(timing: .early).count, 2) + + subject.onChange = { receivedVal0 = $0!; expectOriginal0.fulfill() } + + subject.value = 99 + + wait(for: [expectOriginal0, expectRepeater1, expectRepeater2], timeout: 8.0) + XCTAssertEqual(receivedVal0, 99) + XCTAssertEqual(receivedVal1, 99) + XCTAssertEqual(receivedVal2, 99) + } + + func test_given_it_has_late_repeaters_with_default_filter_when_input_set_then_it_echoes_to_them_as_well() { + var receivedVal0: Int = -1 + var receivedVal1: Int = -1 + var receivedVal2: Int = -1 + let expectOriginal0 = expectation(description: "should dispatch value") + let expectRepeater1 = expectation(description: "should echo value to repeater1") + let expectRepeater2 = expectation(description: "should echo value to repeater2") + let repeater1 = QLAnchor(onChange: { receivedVal1 = $0!; expectRepeater1.fulfill() }) + let repeater2 = QLAnchor(onChange: { receivedVal2 = $0!; expectRepeater2.fulfill() }) + let subject = QLAnchor(lateRepeaters: repeater1, repeater2) + XCTAssertEqual(subject.getRepeaters(timing: .late).count, 2) subject.onChange = { receivedVal0 = $0!; expectOriginal0.fulfill() } @@ -113,7 +136,9 @@ final class QLAnchorTests: XCTestCase { onError: { receivedErr1 = $0; expectRepeater1.fulfill() }) let repeater2 = QLAnchor(onChange: { _ in }, onError: { receivedErr2 = $0; expectRepeater2.fulfill() }) - let subject = QLAnchor(repeaters: repeater1, repeater2) + let subject = QLAnchor(earlyRepeaters: [repeater1], lateRepeaters: [repeater2]) + XCTAssertEqual(subject.getRepeaters(timing: .early).count, 1) + XCTAssertEqual(subject.getRepeaters(timing: .late).count, 1) subject.onError = { receivedErr0 = $0; expectOriginal0.fulfill() } @@ -125,7 +150,34 @@ final class QLAnchorTests: XCTestCase { XCTAssertNotNil(receivedErr2) } - func test_given_it_has_repeaters_with_custom_filter_when_input_set_then_it_dispatches_then_echoes_to_them_conditionally() { + func test_given_it_has_earlyRepeaters_with_custom_filter_when_input_set_then_it_dispatches_then_echoes_to_them_conditionally() { + var receivedVal0: Int = -1 + var receivedVal1: Int = -1 + var receivedVal2: Int = -1 + let expectOriginal0 = expectation(description: "should dispatch value") + let expectRepeater2 = expectation(description: "should echo value to repeater2") + let repeater1 = QLAnchor(onChange: { receivedVal1 = $0! }) + let repeater2 = QLAnchor(onChange: { receivedVal2 = $0!; expectRepeater2.fulfill() }) + + let subject = QLAnchor( + echoFilter: ({ val, repeater in + return (val == 11 && repeater === repeater1) + || (val == 22 && repeater === repeater2) + }), + earlyRepeaters: repeater1, repeater2 + ) + + subject.onChange = { receivedVal0 = $0!; expectOriginal0.fulfill() } + + subject.value = 22 + + wait(for: [expectOriginal0, expectRepeater2], timeout: 8.0) + XCTAssertEqual(receivedVal0, 22) + XCTAssertEqual(receivedVal1, -1) + XCTAssertEqual(receivedVal2, 22) + } + + func test_given_it_has_lateRepeaters_with_custom_filter_when_input_set_then_it_dispatches_then_echoes_to_them_conditionally() { var receivedVal0: Int = -1 var receivedVal1: Int = -1 var receivedVal2: Int = -1 @@ -139,7 +191,7 @@ final class QLAnchorTests: XCTestCase { return (val == 11 && repeater === repeater1) || (val == 22 && repeater === repeater2) }), - repeaters: repeater1, repeater2 + lateRepeaters: repeater1, repeater2 ) subject.onChange = { receivedVal0 = $0!; expectOriginal0.fulfill() } diff --git a/docs/changelog.md b/docs/changelog.md index 5fe7fc9..a6e02bd 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -5,6 +5,12 @@ ## Change Log +
+ +### 0.1.7 + +- `QLAnchor` repeater timing (early/late) +
### 0.1.6 diff --git a/docs/reference/QLAnchor.md b/docs/reference/QLAnchor.md index 9cf1d9e..1e9aa44 100644 --- a/docs/reference/QLAnchor.md +++ b/docs/reference/QLAnchor.md @@ -20,7 +20,9 @@ - init(onChange: `(Input?)->()`, onError: `(Error)->()` ) -- init(repeaters: `QLAnchor.Repeater`, `...` ) +- init(earlyRepeaters: `QLAnchor.Repeater`, `...` ) + +- init(lateRepeaters: `QLAnchor.Repeater`, `...` )
@@ -75,9 +77,9 @@ Repeaters offer a way to fork multiple streams off of the main path. When an Anchor has repeaters applied, then it will `echo` any `value` and `error` changes to each of them. -By default, it forwards all changes to all repeaters. In order to make it conditional, we can -set an `EchoFilter`, which gets called prior to forwarding to each repeater. Return `false` -from the EchoFilter to block that repeater from receiving the change. +By default, it forwards all changes to all repeaters. In order to make it conditional, +include an `EchoFilter`. Return `false` from the EchoFilter to block that repeater +from receiving the change. ##### EchoFilter From bfbab0dd86f8e26503ed12401f66cd6d0abea821 Mon Sep 17 00:00:00 2001 From: Quickthyme Date: Tue, 12 Nov 2019 17:38:09 -0600 Subject: [PATCH 2/2] reverted force unwrap Signed-off-by: Quickthyme --- Sources/QLoop/QLAnchor.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Sources/QLoop/QLAnchor.swift b/Sources/QLoop/QLAnchor.swift index 155d361..dbf67c0 100644 --- a/Sources/QLoop/QLAnchor.swift +++ b/Sources/QLoop/QLAnchor.swift @@ -17,7 +17,7 @@ public final class QLAnchor: AnyAnchor { internal final class Repeater { - weak var anchor: QLAnchor! + weak var anchor: QLAnchor? let timing: Timing @@ -27,13 +27,14 @@ public final class QLAnchor: AnyAnchor { } func echo(value: Input?, filter: EchoFilter, timing: Timing) { - if filter(value, anchor) { - anchor.value = value + if let repeater = self.anchor, + filter(value, repeater) { + repeater.value = value } } func echo(error: Error) { - anchor.error = error + anchor?.error = error } }