What's the easiest way to persist data in an iPhone app? - iphone

I want to persist a very simple string. For example "abc". What's the easiest way to do this? I don't want to use a SqlLite database.

If it is just one single string NSUserDefaults is probably the easiest way.
// write
[[NSUserDefaults standardUserDefaults] setObject:#"abc" forKey:#"MY_PERSISTENT_KEY"];
// read
NSString *abc = [[NSUserDefaults standardUserDefaults] valueForKey:#"MY_PERSISTENT_KEY"];

You can make your objects conform to NSCoding, then write them to a file using an NSKeyedArchiver.
NSString* path = [self pathForDataFile];
NSMutableDictionary* rootObject = [NSMutableDictionary dictionary];
[rootObject setValue: someObject forKey: #"SomeObject"];
// Commit data to file
[NSKeyedArchiver archiveRootObject: rootObject toFile: path];

Related

How do I load and save a plist to and from a string?

I know this sounds like an odd question, but I need to keep a copy of my NSUserDefaults in to a database (my aim is provide a database backup / restore feature, using one file, the database).
So I think I've figured out how to load to a file (although I haven't tried this in xcode).
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults registerDefaults:[NSDictionary dictionaryWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:#"UserDefaults" ofType:#"plist"]]];
I've googled how to save NSUserDefaults to a plist and to a string and back, but haven't found anything.
You can use the asynchronous NSPropertyListSerialization API or just the synchronous convenience methods on NSDictionary.
Checkout the discussion in the NSDictionary Apple Docs on the writeToFile:automatically method for more info on how it works
Also, This article has some good info on Serialization in cocoa generally.
Use the following code should get you on your way.
//Get the user documents directory
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
//Create a path to save the details
NSString *backedUpUserDefaultsPath = [documentsDirectory stringByAppendingPathComponent:#"NSUserDefaultsBackup.plist"];
//Get the standardUserDefaults as an NSDictionary
NSDictionary *userDefaults = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
//The easiest thing to do here is just write it to a file
[userDefaults writeToFile:backedUpUserDefaultsPath atomically:YES];
//Alternatively, you could use the Asynchronous version
NSData *userDefaultsAsData = [NSKeyedArchiver archivedDataWithRootObject:userDefaults];
//create a property list object
id propertyList = [NSPropertyListSerialization propertyListFromData:userDefaultsAsData
mutabilityOption:NSPropertyListImmutable
format:NULL
errorDescription:nil];
//Create and open a stream
NSOutputStream *outputStream = [[NSOutputStream alloc] initToFileAtPath:backedUpUserDefaultsPath append:NO];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
outputStream.delegate = self; //you'll want to close, and potentially dealloc your stream in the delegate callback
[outputStream open];
//write that to the stream!
[NSPropertyListSerialization writePropertyList:propertyList
toStream:outputStream
format:NSPropertyListImmutable
options:NSPropertyListImmutable
error:nil];
When you want to go backwards you can simply do something like:
NSDictionary *dictionaryFromDisk = [NSDictionary dictionaryWithContentsOfFile:backedUpUserDefaultsPath];
Or you could use the stream/NSData approach from NSPropertyListSerialization, which is similar to the way you save it.

Should I use NSUserDefaults or a plist to store data?

I will be storing a few strings (maybe 10-20). I am not sure if I should use NSUserDefaults to save them, or write them out to a plist. What is considered best practice? NSUserDefaults seems like it is less lines of code, therefore quicker to implement.
I'd like to add that these string values will be added/removed by the user.
I am assuming an array, but it will work with dictionaries too.
Userdefaults, Core Data and Plists can all be read/write but if you use a plist you need to pay attention in what dir you put it. See the plist part down below.
Core Data I think it's way too much overkill, it's just strings.
It's supposed to be used when you want to persist more complex objects.
NSUserDefaults:
It's pretty fast and easy to do, though it's supposed to store only user settings.
To write them to the userdefaults:
NSArray *stringsArray = [[NSArray alloc] arrayWithObjects: string1, string2, string3, nil];
[[NSUserDefaults standardUserDefaults] setObject:stringsArray forKey:#"MyStrings"];
[[NSUserDefaults standardUserDefaults] synchronize];
To read the from the userdefaults:
NSArray *stringsArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"MyStrings"];
Plist:
If your strings are going to be modified you will need to write and read a plist but you cant't write into your app's resources.
To have a read/write plist first find the documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *stringsPlistPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Strings.plist"];
Create the array (I am assuming the strings are string1, ...)
NSArray *stringsArray = [[NSArray alloc] arrayWithObjects: string1, string2, string3, nil];
Write it to file
[stringsArray writeToFile:stringsPlistPath atomically:YES];
To read the plist:
Find the documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *stringsPlistPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Strings.plist"];
Read it in:
NSArray *stringsArray = [NSArray arrayWithContentsOfFile:stringsPlistPath];
If you are storing 10-20 strings and are looking for not too many lines of code, core data is certainly much too much overhead. I recommend going with the plist. Not a lot of code:
NSURL *plistURL = [[NSBundle mainBundle] URLForResource:#"MyStrings" withExtension:#"plist"];
NSArray *stringArray = [NSArray arrayWithContentsOfURL:plistURL];
iOS ultimately stores all NSUserDefaults data to a plist file. So it will not affect the performance if that is your concern. I personally prefer using NSUserDefaults for small data and plist for a relatively large set of data.
Note: Never store any sensitive information in NSUserDefaults as anyone can see that data.
NSUserDefaults will store the user preferences into a file into the Library/Preferences folder. In theory it serves only to store some application/user properties.
Plist file are usefull to manage a single file. If you need to manage more you should use the Coredata.
There is no restriction about the size of the plist file. Otherwise you have to be careful with plist file because when you need to save or read it the entire contents of the file will be load into memory.
Using .plist
Create a plist using Xcode
Write a value to plist
NSURL *plistURL = [[NSBundle mainBundle] URLForResource:#"settings" withExtension:#"plist"];
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfURL:plistURL];
[dict setValue:#"value" forKey:#"key"];
[dict writeToURL:plistURL atomically:YES];
Read a value from plist
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfURL:plistURL];
NSString *myValue = [dict valueForKey:#"key"];
It depends on what you want to store and why. NSUserDefaults is meant for storing user preferences. You can try to use it for other things, but you probably shouldn't.
Otherwise, if your needs are simple a plist file is pretty straightforward. You can also use core data or come up with your own file format. In general, I use plist for simple tasks and then move to core data for anything more complex.
Using a plist is a good choice for storing your strings if the strings are not just user settings that can go in NSUserDefaults. As was mentioned, when using a plist you must store your plist in the Documents directory in order to write to it, because you can't write into your own app's resources. When I first learned this, I wasn't clear on where your own app's Bundle directory was vs. where the Documents directory was, so I thought I'd post example code here that first copies a plist called "Strings.plist" (that you already have in your own app's Bundle directory) to the Documents directory, and then writes to it and reads from it.
// Make a path to the plist in the Documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *stringsPlistPathIndDoc = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Strings.plist"];
// Make a path to the plist in your app's Bundle directory
NSString *stringsPlistPathInBundle = [[NSBundle mainBundle] pathForResource:#"Strings" ofType:#".plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
// Check first that you haven't already copied this plist to the Documents directory
if (![fileManager fileExistsAtPath:stringsPlistPathIndDoc])
{
NSError *error;
// Copy the plist from the Bundle directory to the Documents directory
[fileManager copyItemAtPath:stringsPlistPathInBundle toPath:stringsPlistPathIndDoc error:&error];
}
// Write your array out to the plist in the Documents directory
NSMutableArray *stringsArray = [[NSMutableArray alloc] initWithObjects:#"string1", #"string2", #"string3", nil];
[stringsArray writeToFile:stringsPlistPathIndDoc atomically:YES];
// Later if you want to read it:
stringsArray = [[NSMutableArray alloc] initWithContentsOfFile:stringsPlistPathIndDoc];
NSUSerDefaults is indeed quick to implement, but mostly as your application grows, you want to store more and more, I went directly for plist files.
Mostly, people want to store a list of something, so here is my share on how to do this with NSDictionary. This does not require you to create a plist file first, it will be created at the first time saving something
xcode 7 beta, Swift 2.0
saving
func SaveItemFavorites(items : Array<ItemFavorite>) -> Bool
{
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let docuDir = paths.firstObject as! String
let path = docuDir.stringByAppendingPathComponent(ItemFavoritesFilePath)
let filemanager = NSFileManager.defaultManager()
let array = NSMutableArray()
for var i = 0 ; i < items.count ; i++
{
let dict = NSMutableDictionary()
let ItemCode = items[i].ItemCode as NSString
dict.setObject(ItemCode, forKey: "ItemCode")
//add any aditional..
array[i] = dict
}
let favoritesDictionary = NSDictionary(object: array, forKey: "favorites")
//check if file exists
if(!filemanager.fileExistsAtPath(path))
{
let created = filemanager.createFileAtPath(path, contents: nil, attributes: nil)
if(created)
{
let succeeded = favoritesDictionary.writeToFile(path, atomically: true)
return succeeded
}
return false
}
else
{
let succeeded = notificationDictionary.writeToFile(path, atomically: true)
return succeeded
}
}
Little note from the docs:
NSDictionary.writeToFile(path:atomically:)
This method recursively validates that all the contained objects are property list objects (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary) before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
So whatever you set at dict.SetObject() should be one of the above mentioned types.
loading
private let ItemFavoritesFilePath = "ItemFavorites.plist"
func LoadItemFavorites() -> Array<ItemFavorite>
{
let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true) as NSArray
let docuDir = paths.firstObject as! String
let path = docuDir.stringByAppendingPathComponent(ItemFavoritesFilePath)
let dict = NSDictionary(contentsOfFile: path)
let dictitems : AnyObject? = dict?.objectForKey("favorites")
var favoriteItemsList = Array<ItemFavorite>()
if let arrayitems = dictitems as? NSArray
{
for var i = 0;i<arrayitems.count;i++
{
if let itemDict = arrayitems[i] as? NSDictionary
{
let ItemCode = itemDict.objectForKey("ItemCode") as? String
//get any additional
let ItemFavorite = ItemFavorite(item: ItemCode)
favoriteItemsList.append(ItemFavorite)
}
}
}
return favoriteItemsList
}
The recommended way to persist data like this is to use Core Data. While NSUserDefaults can be used to store more or less anything it's only supposed to be used to store preferences.

Get NSUserDefaults plist file from device

When testing my app on the simulator, I like the ability to edit, or even trash the apps plist file (which contains the NSUserDefaults) from the iPhone Simulator folder. This proves useful when testing (e.g. your app stores a dictionary in there, but you change the model/keys that you use for this data, and therefore need to remove the dictionary stored).
Is it possible to access this file on device (for your own app), without jailbreak?
Thanks in advance
The file is in Library/Preferences. The file is a binary plist with name <iOS application target identifier>.plist (look for the Identifier field in your app target settings), or list the directory contents:
NSString *path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];
NSArray *dirContents = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:nil];
You could also load clean defaults with a #ifdef macro based on some env variable:
#ifdef TESTING
// use the code provided by tsakoyan below
#endif
If you care only for the NSUserDefaults values, this should trash/restore to global defaults all its custom data
NSDictionary *userDefDic = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
NSArray *keys = [NSArray arrayWithArray:[userDefDic allKeys]];
for (NSString *key in keys) {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:key];
}
[[NSUserDefaults standardUserDefaults] synchronize];

How do I append a new item to a plist?

In my iPhone app, I have two plist files to store "Themes". One is a read-only file containing default themes, and one contains custom Themes that the user has created. I'm using plist files because it's very easy for me to read from the plist and create new Theme objects.
My plist is an array of dictionary objects.
Is there any easy way to append a new dictionary object to my plist file? Or do I need to read the file into memory, append the new dictionary object, and write it back to the filesystem?
Thanks!
With Cocoa, you need to read the file into memory, append the new dictionary object, and write it back to the filesystem. If you use an XML plist, you could pretty easily parse it and incrementally write to the file, but it'd also be quite a bit bigger, so it's unlikely to be worth it.
If rewriting the plist is taking too long, you should investigate using a database instead (perhaps via Core Data). Unless the file is huge, I doubt this will be an issue even with the iPhone's memory capacity and flash write speed.
(I copied this for those who don't want to click a link from a similar question I answered here: A question on how to Get data from plist & how should it be layout)
Here are two methods to read and write values from a plist using an NSDictionary:
- (NSMutableDictionary*)dictionaryFromPlist {
NSString *filePath = #"myPlist.plist";
NSMutableDictionary* propertyListValues = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
return [propertyListValues autorelease];
}
- (BOOL)writeDictionaryToPlist:(NSDictionary*)plistDict{
NSString *filePath = #"myPlist.plist";
BOOL result = [plistDict writeToFile:filePath atomically:YES];
return result;
}
and then in your code block somewhere:
// Read key from plist dictionary
NSDictionary *dict = [self dictionaryFromPlist];
NSString *valueToPrint = [dict objectForKey:#"Executable file"];
NSLog(#"valueToPrint: %#", valueToPrint);
// Write key to plist dictionary
NSString *key = #"Icon File";
NSString *value = #"appIcon.png";
[dict setValue:value forKey:key];
// Write new plist to file using dictionary
[self writeDictionaryToPlist:dict];
This is how I am appending data to the plist:
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
[array addObject:countdownLabel.text];
[array writeToFile:[self dataFilePath] atomically:YES];
[array release];
}
else
{
NSArray *array = [NSArray arrayWithObject:countdownLabel.text];
[array writeToFile:filePath atomically:YES];
}

Save additional information to a plist every time I run the app? (iphone)

My iphone app writes key-value pairs to a dictionary in a plist file. I'm basically saving the user's score when they play the game. This is all fine and dandy, but each time I run the app and get new scores, the new values get saved over the old values. How do I add information to the plist each time the user accesses the app instead of rewriting the file? I want to keep all of the scores, not just the most recent one.
code:
-(void)recordValues:(id)sender {
//read "propertyList.plist" from application bundle
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path
stringByAppendingPathComponent:#"propertyList.plist"];
dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:finalPath];
//create an NSNumber object containing the
//float value userScore and add it as 'score' to the dictionary.
NSNumber *number=[NSNumber numberWithFloat:userScore];
[dictionary setObject:number forKey:#"score"];
//dump the contents of the dictionary to the console
for (id key in dictionary) {
NSLog(#"memory: key=%#, value=%#", key, [dictionary
objectForKey:key]);
}
//write xml representation of dictionary to a file
[dictionary writeToFile:#"/Users/rthomas/Sites/propertyList.plist" atomically:NO];
}
You are setting the object to a number for key score
NSNumber *number=[NSNumber numberWithFloat:userScore];
[dictionary setObject:number forKey:#"score"];
Instead of this what you want to do is have an array or something of the sort so
NSNumber *number=[NSNumber numberWithFloat:userScore];
NSMutableArray *array=[dictionary objectForKey:#"score"]
[array addObject:number]
[dictionary setObject:array forKey:#"score"]
this should do what you are asking
You want to load the old values first, in a NSArray or NSDictionary like Daniel said.
The you add the new value to the collection. (maybe do some sorting or something also)
Then you write the new collection back to disk.