Replies: 1 comment 4 replies
-
I now had a time to make a repro sample. It's still annoying to test as it requires a device and rebooting. To reproduce the prewarming scenario:
The sample allows to test various setups with UserDefaults, Shared and AppStorage and different launch configurations where Shared or UserDefaults is accessed in a locked prewarming state or not. Compared to my previous testing there's an apparent fix for iOS 18 where later accesses to UserDefaults update the in memory cache from the file cache so only the initial value is wrong. I have to test this sample again on an iOS 17 device later. AppStorage performs better in my sample as it refreshes correctly some time after the unlocking. While Shared in most cases use the default value potentially overriding actual values when the value gets mutated. I don't see this as a swift-sharing library issue, but as a very complicated iOS app lifecycle issue where it would be very desirable to have mitigations in place. I can share my custom persistence keys that I use as a workaround in my apps. Live-Activity-Example-main.zip This discussion should be moved to swift-sharing by the way. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Probably you've seen this blog post that was shared a lot recently on UserDefaults and app prewarming.
https://christianselig.com/2024/10/beware-userdefaults/
The summary is that iOS in some cases launches apps in the background after a reboot before the first unlock to speed up the perceived app launch once the user interacts with the app. Before the first unlock anything on the file system that has
completeUntilFirstUserAuthentication
is not available.I already once spend quite a lot of time to debug some Core Data setup issue in a group container due to this where I learned that anything in an shared application group container has at least
completeUntilFirstUserAuthentication
. So one must deal with prewarming issues carefully.When I read the blog post I first thought this would only apply to app group user defaults. But I verified in a sample project that
UserDefaults.standard
also is affected. So I guess it's also created withcompleteUntilFirstUserAuthentication
by the system.The biggest problem as written in the article, is if some state is initialized while the device is locked as UserDefaults returns nil. Then the initialValue or some fallback will be used. And at a later launch some mutation based on this initialValue can write incorrect data back to the UserDefaults store.
E.g. the value on disk for an Int is 42, while locked the initialValue of 0 is used instead, and some increment action then writes 1 to the store instead of 43.
It's possible that this doesn't always apply to appStorage as there's the willEntereForground notification observation:
So I guess if the mutation happens after a foreground event the event will have already loaded the actual value. But in app extensions and some prewarming or background scenarios there can be a write before the app enters foreground. For example background URL session or background push may trigger some work without the app opening.
My question would be, if that's something you'd address in the library or not. Fundamentally UserDefaults has the same issues so I'd understand, if you would say that developers need to be aware of it and make their own adjustments. On the other hand this is a quite annoying thing to deal with and also something many developers won't be aware of as the whole thing is not documented much by Apple. So it would be nice to have protections against this scenario abstracted away in the PersistenceKey.
As far as I understand guarding against this scenario would require checking, if the device is in a state where protected data is unavailable and then refreshing the state once that changes. For the first check one can write an empty file to a temp dir with
completeUntilFirstUserAuthentication
, which will throw if it's not available. I don't know, if it's possible to use a DispatchSource to be notified when such a file becomes available in app extensions.If UIApplication is available one can use
isProtectedDataAvailable
,protectedDataDidBecomeAvailableNotification
,protectedDataWillBecomeUnavailableNotification
. One can also use a Keychain item to check this. Maybe there's more ways to do this check.https://nemecek.be/blog/104/checking-if-device-is-locked-or-sleeping-in-ios
Not really related to this I also checked the fileStorage implementation and saw that for loading the initialValue is used as fallback, if something is thrown and for saving thrown errors are just ignored with
try?
. Would it not make sense here to use the reportIssue API?Beta Was this translation helpful? Give feedback.
All reactions