UserDefaults.standard setValue forKey crash on iOS 15 only.

This crash happens on iOS 15 later only,how can I find the real reason?

My code:

Crash log:

Replies

It's good to post screenshot but you should also post as code text, that's easier to handle.

  • where does it crash exactly: on targets definition ? on traces definition ?
  • which Xcode version (should test with 13.2..1)
  • How is queue defined ?

Note: you could rewrite the test as

            let newTrace = (trace != nil && !trace!.isEmpty) ? trace! : "0"
  • Thank you for your advice, but this crash is an online crash, and we could not reproduce it. And the queue had been defined just like this:

    private let queue = DispatchQueue(label: "***.hermes.cache")

Add a Comment

setValue(forKey:) is a key-value coding (KVC) method. You should be using the UserDefaults specific method, namely set(forKey:). I don’t think that’ll fix this problem, but it’s the right thing to do regardless.

Also, the screen shot you posted doesn’t align with the crash report. The former indicates a force unwrap issue whereas the latter shows a crash deep within UserDefaults. Are you chasing two issues? If not, why this disparity?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks, but the screen shot just show the last method called in my code with that crash log, the force unwrap issue just a warning.

Add a Comment

Does anyone find the wayout? I got same question when invoke [[NSUserDefaults standardUserDefaults] setObject:value forKey:key] in sub thread.

  

That crash report suggests that you’re crashing here. This is deep within the Objective-C runtime, where it’s doing a pointer authentication on a class pointer. This is usually due to some sort of memory management bug, but as to whether that’s your bug or a bug in the system it’s hard to say. We’re deep in KVO here, and KVO has known race conditions that could cause a crash like this.

To start, I recommend that you test your app thoroughly with the standard memory debugging tools. The goal here is that, if there is a memory mangement bug on your side, this will help flush it you so that you can fix it.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Awesome for reply so soon! : )

Add a Comment

I've noticed that AppStorage uses KVO to observe NSUserDefaults. Is it possible that using AppStorage causes writing to NSUserDefaults to no longer be thread-safe, since SwiftUI will get notified on the wrong thread?

After further investigation, I have confirmed the following (submitted as FB12348064):

The AppStorage property wrapper uses KVO to observe UserDefaults. This makes writing to UserDefaults unsafe on non-main threads, even if the AppStorage is not using the same key as is being written. Any write to UserDefaults causes a lookup of all observers. If the AppStorage is deallocated during that lookup, there will be a crash.

The following reliably crashes for me within a few hundred iterations, tested with both Xcode 14.3.1 and 15b1:

import SwiftUI

struct ContentView: View {
    @State var n: Int = 0

    // This song-and-dance is to make sure that AppStorageView is destroyed and recreated.
    // The two views are visually identical to make the output easier to read. One has @AppStorage,
    // the other does not. This causes very fast registering/deregistering from UserDefaults KVO.
    func bodyView() -> AnyView {
        if n % 2 == 0 {
            return AnyView(
                ForEach(0..<10) { _ in
                    AppStorageView(n: n)
                })
        } else {
            return AnyView(
                ForEach(0..<10) { _ in
                    NoAppStorageView(n: n)
                })
        }
    }

    var body: some View {
        bodyView()
            .task {
                // Churn UserDefaults on a background thread.
                Task.detached {
                    while true {
                        UserDefaults.standard.set(Date(), forKey: "randomOtherUserDefaultsKey")
                        await Task.yield()
                    }
                }

                // At the same time, churn the Views to create and destroy AppStorage observations.
                while true {
                    n += 1
                    await Task.yield()
                }
            }
    }
}

// View with @AppStorage observation
struct AppStorageView: View {
    var n: Int

    @AppStorage("appStorageValue") var appStorageValue = false

    var body: some View { LogView(n: n) }
}

// View without @AppStorage observation
struct NoAppStorageView: View {
    var n: Int

    var body: some View { LogView(n: n) }
}

struct LogView: View {
    var n: Int
    var body: some View {
        HStack {
            Text("App Storage Test: \(n)")
            Spacer()
        }
    }
}
  • Ouch!

  • Good news is that it does seem fixable. We created our own (slightly feature-limited) drop-in replacement for AppStorage, using PMKVObserver to handle the KVObservations in a thread-safe way, and it works fine. Feels like something Apple should hopefully be able to fix without too much trouble, and we have a solution that we believe will work for us until then.

Add a Comment