在 Sheet 消失后将数据写入 UserDefaults 时发生崩溃 (SIGABRT)

Crash (SIGABRT) when writing data to UserDefaults after Sheet disappears

我收到了三个无法重现的类似崩溃报告(全部在 iOS 14.4 上)。 stracktrace 说明如下(我只粘贴了我的应用程序开始的部分):

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)

发生的事情是,我有一个带按钮的 Sheet,单击它时 sheet 消失,在 onDisappear 中的 changeCounter 方法调用主视图 MyView 来更改 counter。当calling/opening Sheet.

时,方法changeCounterMyView 传递给Sheet

这是MyView中的.sheet方法:

.sheet(item: $activeSheet) { item in
    switch item {
    case .MY_SHEET:
        MySheet(changeCounter: {changeCounter(decrease: true)}, changeTimer, item: $activeSheet)
    }
}

这是(的重要部分)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!()
            }
        })
    }
}

这里是 changeCounterpreferences 对象:

struct MyView: View {
    @EnvironmentObject var preferences: Preferences

    var body: some View {...}

    func changeCounter(decrease: Bool) {
        if decrease {
            preferences.counter -= COUNTER_INTERVAL
        }
    }
}

Preferences 是一个带有 counter 变量的 ObservableObject

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")
        }
    }
}

它更改 userDefaults 中的一个值,即 UserDefaults.standard

任何人都知道崩溃是如何发生的以及在什么情况下发生的?因为它现在在用户设备上只发生了三次,我无法重现。

我们来分析一下

    Button(action: {
        self.dismissAction = changeCounter       1)
        self.item = nil                          2)
    }, label: {

第 1 行更改内部 sheet 状态,启动 sheet 视图的更新 第 2 行)更改外部状态以启动 sheet 的关闭(并且可能更新父视图)。

它甚至听起来像是两个相互冲突的过程(即使没有依赖流,但看你的代码第二取决于第一个的结果)。所以,这是非常危险的逻辑,应该避免。

一般来说,正如我在评论中所写,在一个闭包中更改两个状态总是有风险的,因此我会重写逻辑以使其具有类似(草图)的内容:

Button(action: {
    self.result = changeCounter  // one external binding !!
}, label: {

,即。启动一些外部 activity...

的一个状态变化

您的代码的可能解决方法(如果出于任何原因您无法更改逻辑)是及时分离这些状态的更改,例如

Button(action: {
    self.dismissAction = changeCounter // updates sheet
    DispatchQueue.main.async {         // or after some min delay
      self.item = nil                  // closes sheet after (!) update
    }
}, label: {