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..dbf67c0 100644
--- a/Sources/QLoop/QLAnchor.swift
+++ b/Sources/QLoop/QLAnchor.swift
@@ -11,17 +11,28 @@ 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) {
+
+ let timing: Timing
+
+ init(_ anchor: QLAnchor, timing: Timing) {
+ self.timing = timing
self.anchor = anchor
}
- func echo(value: Input?, filter: EchoFilter) {
+
+ func echo(value: Input?, filter: EchoFilter, timing: Timing) {
if let repeater = self.anchor,
filter(value, repeater) {
repeater.value = value
}
}
+
func echo(error: Error) {
anchor?.error = error
}
@@ -42,9 +53,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 +79,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 +129,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