Skip to content

Commit 313dd21

Browse files
Improving developer experience with runtime warnings (#943)
* Perform thread check only when store is created on main thread. * clean up * Update Sources/ComposableArchitecture/Store.swift * clean up * Update Sources/ComposableArchitecture/Store.swift * clean up * execute setSpecific only once. * logic fix * added a test * typo * wip * wip * wip * clean up * language * wip * note * wip * wip * wip * wip * wip * fix closing quote * wip * fix merge * Deprecations * Fix docs * wip Co-authored-by: Brandon Williams <[email protected]>
1 parent 9c162f4 commit 313dd21

File tree

7 files changed

+361
-217
lines changed

7 files changed

+361
-217
lines changed

Sources/ComposableArchitecture/Internal/Breakpoint.swift

Lines changed: 0 additions & 30 deletions
This file was deleted.

Sources/ComposableArchitecture/Internal/Deprecations.swift

Lines changed: 110 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,95 @@ import Combine
33
import SwiftUI
44
import XCTestDynamicOverlay
55

6+
#if DEBUG
7+
import os
8+
#endif
9+
10+
// NB: Deprecated after 0.31.0:
11+
12+
extension Reducer {
13+
@available(
14+
*,
15+
deprecated,
16+
message: "'pullback' no longer takes a 'breakpointOnNil' argument"
17+
)
18+
public func pullback<GlobalState, GlobalAction, GlobalEnvironment>(
19+
state toLocalState: CasePath<GlobalState, State>,
20+
action toLocalAction: CasePath<GlobalAction, Action>,
21+
environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment,
22+
breakpointOnNil: Bool,
23+
file: StaticString = #fileID,
24+
line: UInt = #line
25+
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment> {
26+
self.pullback(
27+
state: toLocalState,
28+
action: toLocalAction,
29+
environment: toLocalEnvironment,
30+
file: file,
31+
line: line
32+
)
33+
}
34+
35+
@available(
36+
*,
37+
deprecated,
38+
message: "'optional' no longer takes a 'breakpointOnNil' argument"
39+
)
40+
public func optional(
41+
breakpointOnNil: Bool,
42+
file: StaticString = #fileID,
43+
line: UInt = #line
44+
) -> Reducer<
45+
State?, Action, Environment
46+
> {
47+
self.optional(file: file, line: line)
48+
}
49+
50+
@available(
51+
*,
52+
deprecated,
53+
message: "'forEach' no longer takes a 'breakpointOnNil' argument"
54+
)
55+
public func forEach<GlobalState, GlobalAction, GlobalEnvironment, ID>(
56+
state toLocalState: WritableKeyPath<GlobalState, IdentifiedArray<ID, State>>,
57+
action toLocalAction: CasePath<GlobalAction, (ID, Action)>,
58+
environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment,
59+
breakpointOnNil: Bool,
60+
file: StaticString = #fileID,
61+
line: UInt = #line
62+
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment> {
63+
self.forEach(
64+
state: toLocalState,
65+
action: toLocalAction,
66+
environment: toLocalEnvironment,
67+
file: file,
68+
line: line
69+
)
70+
}
71+
72+
@available(
73+
*,
74+
deprecated,
75+
message: "'forEach' no longer takes a 'breakpointOnNil' argument"
76+
)
77+
public func forEach<GlobalState, GlobalAction, GlobalEnvironment, Key>(
78+
state toLocalState: WritableKeyPath<GlobalState, [Key: State]>,
79+
action toLocalAction: CasePath<GlobalAction, (Key, Action)>,
80+
environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment,
81+
breakpointOnNil: Bool,
82+
file: StaticString = #fileID,
83+
line: UInt = #line
84+
) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment> {
85+
self.forEach(
86+
state: toLocalState,
87+
action: toLocalAction,
88+
environment: toLocalEnvironment,
89+
file: file,
90+
line: line
91+
)
92+
}
93+
}
94+
695
// NB: Deprecated after 0.29.0:
796

897
#if DEBUG
@@ -458,36 +547,44 @@ extension Reducer {
458547
return .none
459548
}
460549
if index >= globalState[keyPath: toLocalState].endIndex {
461-
if breakpointOnNil {
462-
breakpoint(
550+
#if DEBUG
551+
os_log(
552+
.fault, dso: rw.dso, log: rw.log,
463553
"""
464-
---
465-
Warning: Reducer.forEach@\(file):\(line)
554+
A "forEach" reducer at "%@:%d" received an action when state contained no element at \
555+
that index. …
556+
557+
Action:
558+
%@
559+
Index:
560+
%d
466561
467-
"\(debugCaseOutput(localAction))" was received by a "forEach" reducer at index \
468-
\(index) when its state contained no element at this index. This is generally \
469-
considered an application logic error, and can happen for a few reasons:
562+
This is generally considered an application logic error, and can happen for a few \
563+
reasons:
470564
471-
* This "forEach" reducer was combined with or run from another reducer that removed \
565+
This "forEach" reducer was combined with or run from another reducer that removed \
472566
the element at this index when it handled this action. To fix this make sure that \
473567
this "forEach" reducer is run before any other reducers that can move or remove \
474568
elements from state. This ensures that "forEach" reducers can handle their actions \
475569
for the element at the intended index.
476570
477-
* An in-flight effect emitted this action while state contained no element at this \
571+
An in-flight effect emitted this action while state contained no element at this \
478572
index. While it may be perfectly reasonable to ignore this action, you may want to \
479573
cancel the associated effect when moving or removing an element. If your "forEach" \
480574
reducer returns any long-living effects, you should use the identifier-based \
481575
"forEach" instead.
482576
483-
* This action was sent to the store while its state contained no element at this \
577+
This action was sent to the store while its state contained no element at this \
484578
index. To fix this make sure that actions for this reducer can only be sent to a \
485579
view store when its state contains an element at this index. In SwiftUI \
486580
applications, use "ForEachStore".
487-
---
488-
"""
581+
""",
582+
"\(file)",
583+
line,
584+
debugCaseOutput(localAction),
585+
index
489586
)
490-
}
587+
#endif
491588
return .none
492589
}
493590
return self.run(
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#if DEBUG
2+
import os
3+
4+
// NB: Xcode runtime warnings offer a much better experience than traditional assertions and
5+
// breakpoints, but Apple provides no means of creating custom runtime warnings ourselves.
6+
// To work around this, we hook into SwiftUI's runtime issue delivery mechanism, instead.
7+
//
8+
// Feedback filed: https://gist.github.com/stephencelis/a8d06383ed6ccde3e5ef5d1b3ad52bbc
9+
let rw = (
10+
dso: { () -> UnsafeMutableRawPointer in
11+
var info = Dl_info()
12+
dladdr(dlsym(dlopen(nil, RTLD_LAZY), "LocalizedString"), &info)
13+
return info.dli_fbase
14+
}(),
15+
log: OSLog(subsystem: "com.apple.runtime-issues", category: "ComposableArchitecture")
16+
)
17+
#endif

0 commit comments

Comments
 (0)