It seems that UserDefaults.standard works on Windows, but the settings are persisted with a file called .plist in the AppData/Local directory (or it is named for the executable if the bundle identifier is not set). When I run my app a directory named for the package or target name is created in AppData/Local/, but the plist file is stored directly in AppData/Local instead of this directory.
There are no other files in the AppData/Local directory, only directories. Other Windows apps don't write files here.
Is it possible to configure UserDefaults.standard on Windows such that the plist file is stored in the subdirectory that is created for my app?
The LocalAppData is similar to “Application Support”. IIRC, macOS will place the plait directly there named after the bundle. Having an option to switch to a directory specific layout seems reasonable.
Our goal is to provide abstractions. However, if one of our supported platforms needs platform-specific options then I think it is a good idea to expose that somehow in the API.
I took a look at the code and I believe that the issue I am referring to should be fixed within swift-corelibs-foundation in CFPreferences.c.
_CFPreferencesURLForStandardDomainWithSafetyLevel calls _preferencesCreateDirectoryForUserHostSafetyLevel() to get the preferences directory, and constructs the filename using the app name, typically as appName.plist. For Windows it then uses CFURLCreateWithFileSystemPathRelativeToBase to store the file in that directory.
_preferencesCreateDirectoryForUserHostSafetyLevel() calls _CFKnownLocationCreatePreferencesURLForUser() to construct the URL. On Windows _CFKnownLocationCreatePreferencesURLForUser() creates a URL ending in username/AppData/Local.
So the appName.plist file is placed directly in AppData/Local.
Instead it would be appropriate to include appName as a directory within the URL. This can perhaps be done in the existing Windows-specific code within _CFPreferencesURLForStandardDomainWithSafetyLevel() or possibly within the Windows-specific code in
_CFKnownLocationCreatePreferencesURLForUser(). Without reading the rest of the code I’m not sure which is more appropriate.
Yes, the code for this is currently in swift-corelibs-foundation, but the point was less where is this going on and more about how does it align with macOS and Linux.
Why enforce consistency between platforms that have different conventions? According to @markokh, Foundation currently stores MyApp.plist directly within AppData but also creates a MyApp directory alongside it. This already differs from Darwin, where MyApp.plist goes in ~/Library/Preferences while the MyApp folder is created within ~/Library/Application Support. Since they’re already inconsistent, why not put MyApp.plist inside AppData\MyApp if it follows the Windows convention?
The consistency is the critical factor for easing development. Having the identical beahaviour means that it is easier to actually maintain parity with other platforms. Otherwise, each time Foundation is changed, we need to consider what the behaviour is on each platform (or rather, having to consider what the behaviour would be on Windows vs the other platforms).
Remapping ~/Library/Preferences is reasonable - but that is really LocalAppData as per Microsoft's recommendations. It just so happens that ~/Library/Application Support also maps to LocalAppData. As a result, it only appears inconsistent, whereas it is internally consistent.
Isn’t this how Foundation development actually works, though? It’s been that way since the Yellow Box days of the 1990s. A single API that might have platform-specific behavior, plus platform-specific options to adjust that behavior as warranted.
I don’t have any hard influence here anymore, but the goal for an abstraction isn’t consistency of implementation. That’s a nice-to-have for the maintainers. If the abstraction is doing the wrong thing in some scenarios, people won’t use it, or they’ll bear complaints from their downstream clients. (“Hey, I installed SwiftyFoo and it’s littering my AppData directory with its plist. No other app on my system is doing that. What gives?”)
Consider a hypothetical similar scenario on macOS: a cross-platform local-storage API creating a “MyApp” folder under Application Support instead of “com.example.MyApp” (as many do!). It’s not the end of the world, as long as “MyApp” is sufficiently unique. But it’s also wrong according to platform conventions, and makes things messier for power end users for no real reason.
There is already Windows specific code, so consistency is not the absolute requirement (i.e., it doesn't go in ~/Library/Preferences/... on all platforms). The current implementation is not consistent with Mac in the strict sense and it's also not consistent with what is expected on Windows. Adding another directory after AppData/ would make it consistent with Windows, which would be updating code that is already specific to Windows.
Carrying any existing plist file contents forward to the new location might be a challenge though. Perhaps a platform-specific option could help the application developer manage that.