Skip to content

Commit 0700bd7

Browse files
committed
[feat]: add swift-log dep & some more logging
1 parent 0061902 commit 0700bd7

File tree

6 files changed

+94
-24
lines changed

6 files changed

+94
-24
lines changed

Package.swift

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,17 @@ let package = Package(
6262
.package(url: "https://github.com/pointfreeco/swift-perception", "1.5.0" ..< "3.0.0"),
6363
.package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "1.3.3"),
6464
.package(url: "https://github.com/pointfreeco/xctest-dynamic-overlay", from: "1.0.0"),
65+
.package(url: "https://github.com/apple/swift-log.git", from: "1.0.0"),
6566
],
6667
targets: [
6768
// MARK: Workflow
6869

6970
.target(
7071
name: "Workflow",
71-
dependencies: ["ReactiveSwift"],
72+
dependencies: [
73+
.product(name: "Logging", package: "swift-log"),
74+
.product(name: "ReactiveSwift", package: "ReactiveSwift"),
75+
],
7276
path: "Workflow/Sources"
7377
),
7478
.target(
@@ -125,7 +129,10 @@ let package = Package(
125129

126130
.target(
127131
name: "WorkflowReactiveSwift",
128-
dependencies: ["ReactiveSwift", "Workflow"],
132+
dependencies: [
133+
"Workflow",
134+
.product(name: "ReactiveSwift", package: "ReactiveSwift"),
135+
],
129136
path: "WorkflowReactiveSwift/Sources"
130137
),
131138
.target(
@@ -139,7 +146,10 @@ let package = Package(
139146

140147
.target(
141148
name: "WorkflowRxSwift",
142-
dependencies: ["RxSwift", "Workflow"],
149+
dependencies: [
150+
"Workflow",
151+
.product(name: "RxSwift", package: "RxSwift"),
152+
],
143153
path: "WorkflowRxSwift/Sources"
144154
),
145155
.target(

Samples/Tuist/Package.resolved

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Workflow/Sources/RenderContext.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,10 @@ public class RenderContext<WorkflowType: Workflow>: RenderContextType {
153153
}
154154

155155
private func assertStillValid() {
156-
assert(isValid, "A `RenderContext` instance was used outside of the workflow's `render` method. It is a programmer error to capture a context in a closure or otherwise cause it to be used outside of the `render` method.")
156+
if !isValid {
157+
WorkflowLogger.logExternal(as: .error, "A `RenderContext` instance was used outside of the workflow's `render` method. It is a programmer error to capture a context in a closure or otherwise cause it to be used outside of the `render` method.")
158+
assertionFailure("A `RenderContext` instance was used outside of the workflow's `render` method. It is a programmer error to capture a context in a closure or otherwise cause it to be used outside of the `render` method.")
159+
}
157160
}
158161
}
159162
}

Workflow/Sources/SubtreeManager.swift

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import Dispatch
18+
import IssueReporting
1819

1920
extension WorkflowNode {
2021
/// Manages the subtree of a workflow. Specifically, this type encapsulates the logic required to update and manage
@@ -76,6 +77,17 @@ extension WorkflowNode {
7677

7778
wrapped.invalidate()
7879

80+
#if DEBUG
81+
var contextEscapeCheck = consume wrapped
82+
if !isKnownUniquelyReferenced(&contextEscapeCheck) {
83+
if let _ = TestContext.current {
84+
reportIssue("Detected escape of a RenderContext<\(WorkflowType.self)> instance from a `render()` method. A RenderContext is only valid to use within render(); Allowing it to escape can lead to incorrect behavior.")
85+
} else {
86+
assertionFailure("Detected escape of a RenderContext<\(WorkflowType.self)> instance from a `render()` method. A RenderContext is only valid to use within render(); Allowing it to escape can lead to incorrect behavior.")
87+
}
88+
}
89+
#endif
90+
7991
/// After the render is complete, assign children using *only the children that were used during the render
8092
/// pass.* This means that any pre-existing children that were *not* used during the render pass are removed
8193
/// as a result of this call to `render`.
@@ -409,27 +421,28 @@ extension WorkflowNode.SubtreeManager {
409421
case .valid(let handler):
410422
handler(event)
411423

412-
case .invalid:
424+
case .invalid where isReentrantCall:
413425
#if DEBUG
414426
// Reentrancy seems to often be due to UIKit behaviors over
415427
// which we have little control (e.g. synchronous resignation
416428
// of first responder after a new Rendering is assigned). Emit
417429
// some debug info in these cases.
418-
if isReentrantCall {
419-
print("[\(WorkflowType.self)]: ℹ️ Sink sent another action after it was invalidated but before its original action handling was resolved. This new action will be ignored. If this is unexpected, set a Swift error breakpoint on `\(InvalidSinkSentAction.self)` to debug.")
420-
}
421-
422-
do {
423-
throw InvalidSinkSentAction()
424-
} catch {}
430+
print("[\(WorkflowType.self)]: ℹ️ Sink sent another action after it was invalidated but before its original action handling was resolved. This new action will be ignored. If this is unexpected, set a Swift error breakpoint on `\(InvalidSinkSentAction.self)` to debug.")
431+
do { throw InvalidSinkSentAction() } catch {}
425432
#endif
426433

434+
case .invalid:
427435
// If we're invalid and this is the first time `handle()` has
428436
// been called, then it's likely we've somehow been inadvertently
429437
// retained from the 'outside world'. Fail more loudly in this case.
430-
assert(isReentrantCall, """
431-
[\(WorkflowType.self)]: Sink sent an action after it was invalidated. This action will be ignored.
432-
""")
438+
if !isReentrantCall {
439+
WorkflowLogger.logExternal(as: .warning, """
440+
[\(WorkflowType.self)]: Sink sent an action after it was invalidated. This action will be ignored.
441+
""")
442+
assertionFailure("""
443+
[\(WorkflowType.self)]: Sink sent an action after it was invalidated. This action will be ignored.
444+
""")
445+
}
433446
}
434447
}
435448

Workflow/Sources/WorkflowLogger.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import Foundation
18+
import Logging
1819
import os.signpost
1920

2021
extension OSLog {
@@ -207,3 +208,33 @@ enum WorkflowLogger {
207208
}
208209
}
209210
}
211+
212+
// MARK: - External Logging
213+
214+
typealias EternalLogger = Logging.Logger
215+
216+
extension WorkflowLogger {
217+
static func logExternal(
218+
as level: EternalLogger.Level,
219+
_ message: @autoclosure () -> EternalLogger.Message,
220+
metadata: @autoclosure () -> EternalLogger.Metadata? = nil,
221+
source: @autoclosure () -> String? = nil,
222+
file: String = #fileID,
223+
function: String = #function,
224+
line: UInt = #line
225+
) {
226+
EternalLogger.workflow.log(
227+
level: level,
228+
message(),
229+
metadata: metadata(),
230+
source: source(),
231+
file: file,
232+
function: function,
233+
line: line
234+
)
235+
}
236+
}
237+
238+
extension EternalLogger {
239+
static let workflow = Logger(label: "com.squareup.workflow")
240+
}

Workflow/Tests/SubtreeManagerTests.swift

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17+
import IssueReporting
1718
import ReactiveSwift
1819
import XCTest
20+
1921
@testable import Workflow
2022

2123
final class SubtreeManagerTests: XCTestCase {
@@ -126,16 +128,18 @@ final class SubtreeManagerTests: XCTestCase {
126128

127129
var escapingContext: RenderContext<ParentWorkflow>!
128130

129-
_ = manager.render { context -> TestViewModel in
130-
XCTAssertTrue(context.isValid)
131-
escapingContext = context
132-
return context.render(
133-
workflow: TestWorkflow(),
134-
key: "",
135-
outputMap: { _ in AnyWorkflowAction.noAction }
136-
)
131+
withExpectedIssue {
132+
_ = manager.render { context -> TestViewModel in
133+
XCTAssertTrue(context.isValid)
134+
escapingContext = context
135+
return context.render(
136+
workflow: TestWorkflow(),
137+
key: "",
138+
outputMap: { _ in AnyWorkflowAction.noAction }
139+
)
140+
}
141+
manager.enableEvents()
137142
}
138-
manager.enableEvents()
139143

140144
XCTAssertFalse(escapingContext.isValid)
141145
}

0 commit comments

Comments
 (0)