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
Reply to Slow launch of app on iOS Simulator 26.4
I'm seeing it take 1-5 minutes on launch on Xcode 26.4 (most often around 2m). As suggested, when turning off "debug executable," it launches as expected. FB22353872. I've added the requested traces. This also happens when running unit tests via Cmd-U (which is also configured with "debug executable").
Replies
Boosts
Views
Activity
Mar ’26
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:
Replies
Boosts
Views
Activity
Jun ’23
Reply to UserDefaults.standard setValue forKey crash on iOS 15 only.
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?
Topic: App & System Services SubTopic: General Tags:
Replies
Boosts
Views
Activity
Jan ’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:
Replies
Boosts
Views
Activity
Oct ’22
Reply to Audio renderer fails to render CMSampleBuffer
(Moving to a reply rather than a comment to be more easily found.) I believe that interleaving is the key. I see the same symptoms on planar formats, but interleaved formats work, even for 2 channel audio. I expect this is an undocumented requirement (and an Apple bug). Tested on iOS 16.1.
Topic: Media Technologies SubTopic: Audio Tags:
Replies
Boosts
Views
Activity
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:
Replies
Boosts
Views
Activity
Jun ’21