Post

Replies

Boosts

Views

Activity

Reply to UserDefaults.standard setValue forKey crash on iOS 15 only.
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() } } }
Topic: App & System Services SubTopic: General Tags:
Jun ’23
Reply to Instruments: why? “Failed to gain authorization”
To automate @stragerneds's workaround, you can add the following script to the Pre-action for Profiling: # Make sure to set the shell to zsh, not bash # # For Instruments, re-sign binary with get-task-allow entitlement codesign -s - -v -f --entitlements =(echo -n '<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd"\> <plist version="1.0"> <dict> <key>com.apple.security.get-task-allow</key> <true/> </dict> </plist>') ${TARGET_BUILD_DIR}/${PRODUCT_NAME} I've written up a more complete discussion at https://cocoaphony.micro.blog/2022/10/29/solving-required-kernel.html.
Topic: Programming Languages SubTopic: Swift Tags:
Oct ’22
Reply to SectionFetchRequest/Result value vs reference
I think I've finally figured out what's going on, but I'm at a loss for how I would be able to guess this subtlety from the code or the documentation. I believe what is happening is that, as was noted in the session, quakes has a mutable getter ("changes to the request are committed whenever the results getter is called"). So if you wrote the obvious code: // This is fine because quakes hasn't changed yet quakes.sectionIdentifier = sortBy.section // This would apply the `sectionIdentifier` change and trigger an unnecessary fetch (?) quakes.sortDescriptors = sortBy.descriptors So the recommended code avoids this by only touching the property wrapper once. Despite quakes and config both being structs, they have reference semantics. But despite both referencing the same "object," they have different access semantics due to the property wrapper. This seems incredibly subtle, contrary to standard Swift value/reference semantics, and unmentioned in the documentation. Am I understanding it correctly? Is there any way I should be able guess this behavior?
Topic: UI Frameworks SubTopic: SwiftUI Tags:
Jun ’21