Search code examples
iosswiftswiftuiswiftui-listswiftui-navigationlink

SwiftUI @AppStroage - 'Simultaneous accesses to *, but modification requires exclusive access'


In my project I was dealing with this issue. After many hours, I was able to track down the causes and reduce them into a small demo, but do not understand the real issue. As you can see in the below code there are many things at play here, and the bug doesn't surface if any of the "safe" alternatives mentioned below are used. Is this a Swift Memory Access issue, or a SwiftUI bug?

Steps to reproduce:

  1. Copy the below code into a project
  2. Build on an iPad mini
  3. Tap the back button

Crash

import SwiftUI


/*

 Reproduction Steps:
 ===================
 
 
 #1.  Build on iPad mini (other devices will work in split screen, but Detail view must be showing, sidebar view must be hidden)
 #2.  Tap Back Button

 =====================
 Crash
 Thread 1: Simultaneous accesses to 0x7f8af986bae0, but modification requires exclusive access
 
 // Note: if any of the "safe" alternatives are used below, the bug won't surface.
 
 */

// MARK: - Model

public struct SafeSetting<V> {
    public let key: String
    public let defaultValue: V
    
    public init(name: String, defaultValue: V) {
        self.key =  name
        self.defaultValue = defaultValue
    }
}

public struct CrashSetting<V> {
    public let key: String
    public let defaultValue: V
    
    public init(namespace: String, name: String, defaultValue: V) {
        /// - NOTE:  I believe this is the main issue, but why?
        self.key = String(namespace + "." + name)
        self.defaultValue = defaultValue
    }
}

enum Settings {
    static let safe = SafeSetting(name: "safe", defaultValue: "")
    static let crash = CrashSetting(namespace: "com.mynamespace", name: "crash", defaultValue: "")
}

// MARK: - View

@main
struct NavigationLinkCrashApp: App {
    var body: some Scene {
        WindowGroup {
            RootView()
        }
    }
}

struct RootView: View {
    // MARK: Safe
    /*
    @AppStorage(Settings.safe.key)
    var safe = Settings.safe.defaultValue
    */
    
    
    // MARK: Crash
    @AppStorage(Settings.crash.key)
    var crash = Settings.crash.defaultValue
    
    @ViewBuilder var content: some View {
        NavigationView {
            Sidebar()
            Color.yellow
        }
    }
    var body: some View {
        content
    }
}


struct Sidebar: View {
    var body: some View {
        // List { NavigationLink } hierarchy is needed!
        List {
            NavigationLink {
                Color.red
            } label: {
                // MARK: Safe
                /// `Text("")`
                /// `Label("", systemImage: "circle")`
                /// `Color.green`
                
                // MARK: Crash
                Text("crashes")
                //Label("crashes", systemImage: "circle")
            }
        }
        // List Style is needed!
        
        // MARK: Safe
        //.listStyle(SidebarListStyle())
        //.listStyle(InsetListStyle())
    
        
        // MARK: Crash
        .listStyle(InsetGroupedListStyle())
        //.listStyle(PlainListStyle())
        //.listStyle(GroupedListStyle())
    }
}

Solution

  • I am going to attribute this to a SwiftUI bug. I have submitted it to Apple (FB9975464). The issue seemed to be in the CrashSetting initializer, but it actually occurs without it. I believe the issue is using the dot (.) character in the AppStorage key. It has also caused [error] precondition failure: setting value during update: 5848. I haven't been able to produce a small demo with that crash, but removing the "." from my keys also solves it. If you experience any of the above, specifically while transitioning/navigating views, it's worth checking your AppStorage keys.

    Simplest Crash Demo

    1. Build on iPad mini
    2. Tap Back button
    import SwiftUI
    
    @main
    struct AppStorage_Key_IssueApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    
    struct ContentView: View {
        // Using the "." character results in many hard to track crashes
        @AppStorage("a.b")
        var val = ""
        
        var body: some View {
            NavigationView {
                PrimaryView()
                Color.blue
            }
        }
    }
    
    struct PrimaryView: View {
        var body: some View {
            List {
                NavigationLink {
                    Color.red
                } label: {
                    Text("crashes")
                }
            }
            .listStyle(InsetGroupedListStyle())
        }
    }