Where is a Mac Application's NSUserDefaults Data Stored? - objective-c

I am using NSUserDefaults to store some data in my application.
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
[prefs setObject:#"dummy string" forKey:#"lastValue"];
[prefs synchronize];
For testing purposes I need to see the System Preferences plist file where my NSUserDefaults data is saving on the Mac.
I know where the iOS application user defaults are stored, but I don't know about mac application. Where is a Mac Application's NSUserDefaults Data Stored?

They can be found in more than one place:
~/Library/Preferences/com.example.myapp.plist
~/Library/SyncedPreferences/com.example.myapp.plist
and if sandboxed
~/Library/Containers/com.example.myapp/Data/Library/Preferences/com.example.myapp.plist
~/Library/Containers/com.example.myapp/Data/Library/SyncedPreferences/com.example.myapp.plist

In ~/Library/Preferences/com.example.myapp.plist.

(Xcode 7.3.1,macOS 10.11.6)
For Additional,if you are using App Groups
if let prefs = NSUserDefaults(suiteName: "group.groupApps") {
...
}
plist file will be here:
~/Library/Group Containers/group. groupApps/Library/Preferences/group.groupApps.plist

On Sierra, I found the data here: ~/Library/Application Support/.

One more possible location for these data comes into play when trying things out in a Playground. I was experimenting with UserDefaults in a Playground, using XCode 8.3 and Swift 3, and wanted to see the resulting plist file. After some detective work (UserDefaults files have the bundle identifier in the filename and calling Bundle.main.bundleIdentifier in a Playground gives the XCode identifier) I found to my great surprise that the UserDefaults data was added to:
~/Library/Preferences/com.apple.dt.Xcode
In other words, keys and values are added to the XCode preferences file! I double-checked by coming up with very unlikely strings for the keys and they were indeed added there. I did not have the courage to try using some keys that were already in use by XCode but caution seems good here.

Related

Storing data in custom keyboard

I am building a custom keyboard that learns the way you type for smarter auto-correct. In order to learn... I need to be able to STORE data to the users device. I've tried using NSFileManager with NSDocumentsDirectory but nothing is getting saved int he AppExtension. (I tested the code (copy paste) in a regular app (non-app-extension) and it worked). I even enabled "Requests Open Access" in the .plist and re-installed the keyboard... still wouldn't save data.
Is there a way to store data in an app-extension?
Possible solutions I've pondered:
•Maybe creating a contact in the users address book that has my info in it (if app-extensions are allowed to do that), but a user might be suspicious as to why my app is requesting permission to modify their contact's address book).
•Displaying a hidden UIWebView that uses javascript injection to store and read data Safari Javascript Database, but I'm afraid this data might be erased if cache is ever cleared.
edit: now this is no longer working for me on iDevice beta 5 (with requestOpenAccess enabled OR disabled)? But it still works on SIMULATOR beta 5? Hmmm
__ original post below: _
It turns out NSUserDefaults DOES save data, it just does not work on BETA 3 simulator, but did work on BETA 5 iDevice. RequestsOpenAccess does NOT need to be enabled!
NSUserDefaults *defaults = [[NSUserDefaults alloc]initWithSuiteName:#"com.company.keyboard.uniqueCodeHere"];//uniqueCodeHere can be anything... we just have to make sure the SuiteName isn't equal to your info.plist bundle ID so we add a random uniqueCode to the end of this ID.
[defaults setObject:#"myStringData" forKey:#"savedStrings"];
[defaults synchronize];
*Note, SuiteName Can NOT equal bundle ID in info.plist or it doesn't work for some reason...

Objective C reading array from file fails on different locations

I am developing a mac app and I want to put my settings (array of strings) into a file. If that file exists in the same folder as the app it is read and it overwrites default settings.
No problems writing the file but when reading with code:
NSArray* settingsInput = [[NSArray alloc] initWithContentsOfFile:#"./SettingsFile"];
something strange happens. When running the app from XCode (the settings file is in the build/debug folder next to the app) settings are read without a problem. When I archive the app and run it from the desktop, the file cannot be loaded. Even when I copy the app from the build folder to the desktop it does not work.
What could be the reason for this kind of behaviour? How can I read this file?
It may be a better Idea to use the normal prefence system. NSUserDefaults.
There a couple of ways you can do it.
But the idea is to give your app a set of default preference which are registered for you in the correct domain and always with a fresh app.
Using the registerDefaults: from NSUserDefaults.
See Apples documentation NSUserDefaults and its #registerDefaults
But the one I would use is :
Copy a plist file into the supporting files in you Xcode project.
Making sure "Copy files into destination group's folder" is checked. And the "Add to targets is check also"
The plist file should contain your array of strings.
(I created mine. By duplicating another plist in my user preferences. Renaming it. Copying it to the project. Selecting it and editing it to how I needed it. Making sure I use the file menu ->'Save' to save the changes. )
Declare a NSUserDefaults * prefs;
Now in the - (id)init method for the app. you register the contents of the file as your default preferences.
- (id)init
{
self = [super init];
if (self) {
prefs = [NSUserDefaults standardUserDefaults] ;
NSString *registerDefaultsPlistFile= [[NSBundle mainBundle] pathForResource:#"registerDefaults" ofType:#"plist"];
[prefs registerDefaults:[NSDictionary dictionaryWithContentsOfFile: registerDefaultsPlistFile]];
}
return self;
}
You can later make a call to read these preferences.
NSLog(#" arrayOfStrings = %#", [prefs objectForKey:#"arrayOfStrings" ]);
These default preferences are NOT written to file/out unless you make a change to them. By written to file I mean to the applications preference file. Once you do make a change to them then they will be written out into the users preferences and those are what will be used from then on.
You should not rely on the current directory of the app. You should either read from the app bundle (see NSBundle class for get the correct path) or the app's document directory (see NSSearchPathForDirectoriesInDomains(NSDocumentDirectory ...).
The UNIX concept of the current working directory is not commonly used in Mac desktop applications. When launching an app through the Finder it's usually set to the root directory of the boot volume.
You should find another way to determine locations for your settings files. Good spots would be ~/Library/Preferences or ~/Library/Application Support/MyApp. You can get the absolute path to these directories using:
NSString *path = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomain, YES)[0];

Xcode 5.0 NSUserdefaults still get stored preferences after deleted the plist

I got a problem about programming an osx application on Xcode 5 while using the NSUserDefaults. Usually, we just use [[NSUserDefaults standardUserDefaults] setObject:#"This is an object" forKey:#"Test"] to remember a user preference. After that, the application will generate a plist file at ~/Library/Preferences/application.bundle.identifier.plist.
The problem is, after I deleted the plist file, the application could still get the preferences I stored. There is no way to clear that plist even if I tried to clean project, restart xcode, delete the files in derived folder. The only way for me to solve this problem is to restart the system, so I guess there is something stored in the memory. The question is how can I clear these stored preferences? (I don't think it's convenient to clear the preferences by adding code manually in debugging and testing.) And I tried the former version of Xcode 4.x, there's no such problem. Anyone has interest could just create a new cocoa project, and add the code like:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:#"This is an object." forKey:#"Test"];
NSLog(#"%#", [defaults objectForKey:#"Test"]);
under "applicationDidFinishLaunching". Then go and delete ~/Library/Preferences/application.bundle.identifier.plist.
After that, comment the line: [defaults setObject:#"This is an object." forKey:#"Test"];
in your code and run the application again. The console will still show "This is an object."
My environment is Mavericks GM and Xcode 5.0(5a1413).
Hope this is not something just only happened to me and appreciated any help!
This is an OS X issue not directly related to the version of Xcode you are using. Apple's official line is that deleting the plist file to remove preferences has never been officially supported, and in more recent OS X releases it is unreliable due to changes in the way the preferences are stored.
The supported way to remove preferences is to use the defaults command at the terminal, e.g.:
defaults delete application.bundle.identifier
The defaults command can also remove/change individual settings with in the preferences. For full details see man defaults.

Questions about NSUserDefaults

I have a couple of questions about NSUserDefaults in Mac OS X:
When does the NSUserDefaults use the dictionary provided by registerDefaults? Only the first time the application is opened or every time the application is opened?
Where is the information from NSUserDefaults stored?
How can I reset NSUSerDefaults?
Thanks!
Only the first time. But you can force an application to reuse the defaults with the terminal.
~/Library/Preferences/YourIdentifier.plist (e.g. com.apple.finder.plist)
Terminal: defaults delete YourIdentifier (e.g. com.apple.finder)
Code: [NSUserDefaults resetStandardUserDefaults];
Actually it should change. I would try it with other controls like NSTextField. When it doesn't work you're doing it wrong
The idea of binding is exactly what you thought it is.

How do I enable Local Storage in my WebKit-based application?

I have a Cocoa/Objective-C application which embeds a WebKit WebView. I need to turn on database support and local storage. I know it can be done--I have it working in Safari--but I can't find an example of how to set this up in my own application.
I found this (unanswered) SO question which provides an example but, as the original poster mentions, doesn't work. And in fact, the methods he uses (setDatabasesEnabled, setLocalStorageEnabled) aren't defined in my WebKit.framework (Xcode 3.2.5), although they appear to exist if I define them myself.
Can anyone provide an example of how to enable local database storage for a WebKit-based Cocoa application? Many thanks if so!
Update: I've got something working...I was confused by "databases" vs. "local storage", which are apparently quite different things. Here's the code that works:
WebPreferences* prefs = [webView preferences];
[prefs _setLocalStorageDatabasePath:#"~/Library/Application Support/MyApp"];
[prefs setLocalStorageEnabled:YES];
So that works, but it requires the private method _setLocalStorageDatabasePath, which means no App Store for me. So my amended questions is now: is there a way to make this work without using a private method? I found the WebDatabaseDirectory preference key in this answer, which controls where databases go. But I couldn't find a corresponding key for local storage anywhere in the sources. Or is there a way for me to force local storage to use the database, and so the WebDatabaseDirectory key? Any ideas?
I submitted an app using this code to the Mac App Store, and Apple approved it:
Objective-C
WebPreferences* prefs = [webView preferences];
[prefs _setLocalStorageDatabasePath:#"~/Library/Application Support/MyApp"];
[prefs setLocalStorageEnabled:YES];
Swift 3
var prefs: WebPreferences? = webView.preferences
prefs?._setLocalStorageDatabasePath("~/Library/Application Support/MyApp")
prefs?.localStorageEnabled = true
Whether they will continue to approve that, I don't know, but they allowed it for my application as of 2011-01-29. Update: They also approved a version update to the same app, so it has gone through twice.
I'm going to take the Javascript to Objective-C bridge approach and store everything in core data. Set localStorage to false, then build a JS object and an instance named "localStorage" with the same methods. My javascript devs won't know the difference, and I already had to do the same thing with Air (basically). There's another way to leave the localStorage intact even though it doesn't actually store them in a persistent db. The elements can be iterated through in javascript and manipulated from there, but I think it will be better to simply replace the object with my own.
After a lot of pain and frustration I found a way to enable local storage and have it persist across application runs properly. This solution is specifically for OSX, but it may be applicable to iOS as well.
Download and add this header file into your project. It's not included in the XCode Webkit distribution.
click to download WebStorageManagerPrivate.h
Add to it, the following lines:
static NSString* _storageDirectoryPath();
+ (NSString *)_storageDirectoryPath;
These allow you to retrieve the directory location of the WebKit local storage tracker database. This is important because due to a bug in WebKit, if you don't store your LocalStorage WebView files in the same directory as the tracker database, they are deleted every other time you run your application. I didn't see a way in the WebStorageManager code to change this location for an individual application. It is always read from the user preferences.
Include the WebStorageManagerPrivate.h in your appDelegate.
#include "WebStorageManagerPrivate.h"
You need to download and include in your project another header not included in XCode distribution. Save it as WebPreferencesPrivate.h
click to download WebPreferencesPrivate.h
Include the WebPreferencesPrivate.h in your appDelegate.
#include "WebPreferencesPrivate.h"
Now use the code below in your applicationDidFinishLaunching handler to initialize and enable LocalStorage. The code assumes you have an IBOutlet named 'webView' for the WebView you are using.
NSString* dbPath = [WebStorageManager _storageDirectoryPath];
WebPreferences* prefs = [self.webView preferences];
NSString* localDBPath = [prefs _localStorageDatabasePath];
// PATHS MUST MATCH!!!! otherwise localstorage file is erased when starting program
if( [localDBPath isEqualToString:dbPath] == NO) {
[prefs setAutosaves:YES]; //SET PREFS AUTOSAVE FIRST otherwise settings aren't saved.
// Define application cache quota
static const unsigned long long defaultTotalQuota = 10 * 1024 * 1024; // 10MB
static const unsigned long long defaultOriginQuota = 5 * 1024 * 1024; // 5MB
[prefs setApplicationCacheTotalQuota:defaultTotalQuota];
[prefs setApplicationCacheDefaultOriginQuota:defaultOriginQuota];
[prefs setWebGLEnabled:YES];
[prefs setOfflineWebApplicationCacheEnabled:YES];
[prefs setDatabasesEnabled:YES];
[prefs setDeveloperExtrasEnabled:[[NSUserDefaults standardUserDefaults] boolForKey: #"developer"]];
#ifdef DEBUG
[prefs setDeveloperExtrasEnabled:YES];
#endif
[prefs _setLocalStorageDatabasePath:dbPath];
[prefs setLocalStorageEnabled:YES];
[self.webView setPreferences:prefs];
}
I hope this helps others have struggled or are still struggling with this issue, until it is fixed properly within WebKit.