I got three similar crash reports that I can't reproduce (all on iOS 14.4). The stracktrace says the following (I only pasted the part where my app is starting):
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Triggered by Thread: 0
Thread 0 name:
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x00000001c077d414 __pthread_kill + 8
1 libsystem_pthread.dylib 0x00000001de2d8b50 pthread_kill + 272 (pthread.c:1392)
2 libsystem_c.dylib 0x000000019bc5bb74 abort + 104 (abort.c:110)
3 libswiftCore.dylib 0x0000000196795f20 swift::fatalError(unsigned int, char const*, ...) + 60 (Errors.cpp:393)
4 libswiftCore.dylib 0x0000000196796078 swift::swift_abortRetainUnowned(void const*) + 36 (Errors.cpp:460)
5 libswiftCore.dylib 0x00000001967e5844 swift_unknownObjectUnownedLoadStrong + 76 (SwiftObject.mm:895)
6 SwiftUI 0x00000001992b0cdc ViewGraph.graphDelegate.getter + 16 (ViewGraph.swift:234)
7 SwiftUI 0x00000001997e4d58 closure #1 in GraphHost.init(data:) + 80
8 SwiftUI 0x00000001997e6550 partial apply for closure #1 in GraphHost.init(data:) + 40 (<compiler-generated>:0)
9 AttributeGraph 0x00000001bbcc9b88 AG::Graph::Context::call_update() + 76 (ag-closure.h:108)
10 AttributeGraph 0x00000001bbcca1a0 AG::Graph::call_update() + 56 (ag-graph.cc:176)
11 AttributeGraph 0x00000001bbccfd70 AG::Subgraph::update(unsigned int) + 92 (ag-graph.h:709)
12 SwiftUI 0x00000001997e1cdc GraphHost.runTransaction() + 172 (GraphHost.swift:491)
13 SwiftUI 0x00000001997e4e1c GraphHost.runTransaction(_:) + 92 (GraphHost.swift:471)
14 SwiftUI 0x00000001997e37a8 GraphHost.flushTransactions() + 176 (GraphHost.swift:459)
15 SwiftUI 0x00000001997e2c78 specialized GraphHost.asyncTransaction<A>(_:mutation:style:) + 252 (<compiler-generated>:0)
16 SwiftUI 0x00000001993bd2fc AttributeInvalidatingSubscriber.invalidateAttribute() + 236 (AttributeInvalidatingSubscriber.swift:89)
17 SwiftUI 0x00000001993bd1f8 AttributeInvalidatingSubscriber.receive(_:) + 100 (AttributeInvalidatingSubscriber.swift:53)
18 SwiftUI 0x00000001993bd914 protocol witness for Subscriber.receive(_:) in conformance AttributeInvalidatingSubscriber<A> + 24 (<compiler-generated>:0)
19 SwiftUI 0x000000019956ba34 SubscriptionLifetime.Connection.receive(_:) + 100 (SubscriptionLifetime.swift:195)
20 Combine 0x00000001a6e67900 ObservableObjectPublisher.Inner.send() + 136 (ObservableObject.swift:115)
21 Combine 0x00000001a6e670a8 ObservableObjectPublisher.send() + 632 (ObservableObject.swift:153)
22 Combine 0x00000001a6e4ffdc PublishedSubject.send(_:) + 136 (PublishedSubject.swift:82)
23 Combine 0x00000001a6e76994 specialized static Published.subscript.setter + 388 (Published.swift:0)
24 Combine 0x00000001a6e75f74 static Published.subscript.setter + 40 (<compiler-generated>:0)
25 MyApp 0x00000001005d1228 counter.set + 32 (Preferences.swift:0)
26 MyApp 0x00000001005d1228 Preferences.counter.modify + 120 (Preferences.swift:0)
27 MyApp 0x00000001005ca440 MyView.changeCounter(decrease:) + 344 (MyView.swift:367)
28 MyApp 0x00000001005cf110 0x100584000 + 307472
29 MyApp 0x00000001005e65d8 thunk for @escaping @callee_guaranteed () -> () + 20 (<compiler-generated>:0)
30 MyApp 0x00000001005a8828 closure #2 in MySheet.body.getter + 140 (MySheet.swift:0)
What is happening is, that I have a Sheet with a button and when clicking on it the sheet disappears and in the onDisappear
the changeCounter
method in the main View MyView
is called to change the counter
. The method changeCounter
is passed to the Sheet from MyView
when calling/opening the Sheet.
This is the .sheet method in MyView
:
.sheet(item: $activeSheet) { item in
switch item {
case .MY_SHEET:
MySheet(changeCounter: {changeCounter(decrease: true)}, changeTimer, item: $activeSheet)
}
}
This is the (important part of the) sheet:
struct MySheet: View {
var changeCounter: () -> Void
var changeTimer: () -> Void
@Binding var item: ActiveSheet?
@State var dismissAction: (() -> Void)?
var body: some View {
GeometryReader { metrics in
VStack {
Button(action: {
self.dismissAction = changeCounter
self.item = nil
}, label: {
Text("change_counter")
})
Button(action: {
self.dismissAction = changeTimer
self.item = nil
}, label: {
Text("change_timer")
})
}.frame(width: metrics.size.width, height: metrics.size.height * 0.85)
}.onDisappear(perform: {
if self.dismissAction != nil {
self.dismissAction!()
}
})
}
}
Here is changeCounter
with the preferences
object:
struct MyView: View {
@EnvironmentObject var preferences: Preferences
var body: some View {...}
func changeCounter(decrease: Bool) {
if decrease {
preferences.counter -= COUNTER_INTERVAL
}
}
}
The Preferences
is an ObservableObject
with the counter
variable:
class Preferences: ObservableObject {
let userDefaults: UserDefaults
init(_ userDefaults: UserDefaults) {
self.userDefaults = userDefaults
self.counter = 0
}
@Published var counter: Int {
didSet {
self.userDefaults.set(counter, forKey: "counter")
}
}
}
It changes a value in the userDefaults
that are UserDefaults.standard
.
Anyone has an idea how that crash can happen and in what situations? Because it only happened three times now on users devices and I can't reproduce it.
Let's analyze
Button(action: {
self.dismissAction = changeCounter 1)
self.item = nil 2)
}, label: {
Line 1) changes internal sheet state initiating update of sheet's view Line 2) changes external state initiating close of sheet (and probably update of parent view).
It even sounds as two conflicting process (even if there are no dependent flows, but looking at your code second depends on result of first). So, this is very dangerous logic and should be avoided.
In general, as I wrote in comment, changing two states in one closure is always risky, so I would rewrite logic to have something like (sketch):
Button(action: {
self.result = changeCounter // one external binding !!
}, label: {
, ie. the one state change that initiates some external activity...
Possible workaround for your code (if for any reason you cannot change logic) is to separate changes of those states in time, like
Button(action: {
self.dismissAction = changeCounter // updates sheet
DispatchQueue.main.async { // or after some min delay
self.item = nil // closes sheet after (!) update
}
}, label: {