Saving application data state on exit - iphone

I have an NSMutableArray with 24 strings.
I need to save this data if a user gets a call or quits the application.
I have been looking into many examples but for some reason can’t seem to determine the best way to save the data.
The 24 strings correspond to 24 buttons and their state. When a button is clicked, it displays the corresponding Array info for that buttons tag (0 – 23). What I would need to retain is if 10 buttons where clicked and their data shown, how/what would be the best way of retaining this data so it can be reloaded when the app starts?
I am thinking I would need to store:
Button Tag,
Buttons corresponding Array value,
Button state (whether it has clicked and value is show or not)
I would store this data on exit of the application and then when app is started again, I would determine if this stored data exists, if so populate the array and examine the button states to determine if it had already been shown and if so, set it accordingly. Then when this file was loaded, I would delete the stored data (.DAT file if stored this way). This way if a user quits gracefully, on next start up, it would start a new game.
I have looked at several examples where they store data into a .DAT file but am having problem implementing this….and wondering if this is even the best way.
Any help or thoughts on this is greatly appreciated.
Geo…

you should be able to store it in NSUserDefaults
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:buttonState forKey:#"buttonState"];
[defaults synchronize];
// Then to get the state back
NSMutableArray* buttonState = [[[NSUserDefaults standardUserDefaults] arrayForKey:#"buttonState"] mutableCopy];

You could save the data to a plist in the Documents directory. If the data is there, load it up, and if not, it would suggest a clean run.
To load the data:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documents = [paths objectAtIndex:0];
NSString *filePath = [documents stringByAppendingPathComponent:#"buttonState.plist"];
NSMutableDictionary *buttonState = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
To save the data:
[buttonState writeToFile:filePath atomically: YES];

I save my data by using NSKeyedArchiver/NSKeyedUnarchiver, since your NSMutableArray already supports the NSCoding protocol, you can just use
yourArray = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
[NSKeyedArchiver archiveRootObject:yourArray toFile:file];
Edit:
I suppose NSArray's own methods work also
[NSArray writeToFile:]
[NSArray initWithContentsOfFile:]

Related

iOS - plist file, nsuserdefaults or text data file

I am working on a mobile app which needs a lot of data. Simply put, the data will be for multiple languages and consist of all the words possible for that language. The app would start with only the English language and a lot of it's words. Then the user can choose to download more languages and their data.
I am trying to figure out the best way to read/save/update this data. Should I create a plist file with English data to start with and just keep adding more data as user downloads new languages? Or should I save all the data in nsuserdefaults? Or, should I just include a text file with all the data and parse it on the fly?
Suggestions?
ps: i understand that as this is a mobile app, file space and parsing time have to be considered
Please read my answer here: https://stackoverflow.com/a/7215501/832065
As said in that answer, all NSUserDefaults are stored together in one plist.
A plist and NSUserDefaults are basically the same, however NSUserDefaults should ONLY be used for saving preferences and not a big amount of data. So don't use NSUserDefaults!
I would consider saving this in a plist (NSDictionary). Like this you can have all data sorted in that file. Simply set the "words" (NSString I assume) as object, and the language as key.
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
[dict setObject:words forKey:language];
[dict writeToFile:filePath atomically:YES];
same for reading:
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSString *words = [dict objectForKey:language];
EDIT:
If each word is assigned to a number (you can just use NSArray) it doesn't differ much:
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:filePath];
NSArray *words = [NSArray arrayWithObjects:allTheWords,nil];
[dict setObject:words forKey:language];
[dict writeToFile:filePath atomically:YES];
same for reading:
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSArray *words = [dict objectForKey:language];
NSString *word = [words objectAtIndex:numberAssignedToWord];
I hope this helps! :)
I would recommend using either an SQL database directly or Core Data. If you have a lot of data, you don't want to load that data completely into memory all the time. It is also easier to handle updates, changes or additions of data.
But your question is so general, and doesn't tell anything on what you actually want to do with the data, how large the data is in KByte or MByte, it is hard to give any good answer.
Writing a lot of data into a text file, user defaults, plist or something similar, doesn't seem the right choice. Using Core Data would be the default way to do such things on iOS.
I thing the easiest way would be to handle the data in a NSMutableArray and store it within the application using NSUserDefaults.
You can handle your data within a NSMutableArray, like so:
NSMutableArray *yourDataStuff = [[NSMutableArray alloc] init];
[yourDataStuff addObject:#"Your data 1"];
[yourDataStuff addObject:#"Your data 2"];
[yourDataStuff addObject:#"Your data 3, etc.."];
Then you can store it using NSUserDefaults, like so:
[[NSUserDefaults standardUserDefaults] setObject:yourDataStuff forKey:#"myData"];
And read your data, like so:
yourDataStuff = [[NSUserDefaults standardUserDefaults] objectForKey:#"myData"];
I think you cannot save large data in the NSUserDefault , and you maybe can not do IO with plist , i had done this , when you read or save , you must read in the whole .plist , and you may reveive memory warning , or crash. you can use C (fread,fwrite) write the data in a .txt , and you may read and write with data stream.

Appending Value to NSMutableArray and Saving to NSUserDefaults

I am creating a names generator app for iPhone and this is what I am trying to do.
Allow the user to save the current generated name by clicking a save button.
In order to do this, here is what is happening:
The current babyname is displayed in a UILabel.
The user presses 'save' and then the label.text value is appended to a NSMutableArray (I don't think this is working in my code correctly).
The NSMutableArray is then saved in NSUserDefaults.
The contents will then be loaded in another view to populate UITableView cells.
My specific question is, am I handling this saving/persistent storage process correctly? Here is the snippet in question:
- (IBAction)saveName:(id)sender {
// save current baby name to array
NSMutableArray *babyNameArray = [[NSMutableArray alloc] init];
[babyNameArray addObject:babyname.text];
// save list of babynames to nsuserdefaults
[[NSUserDefaults standardUserDefaults] setObject:babyNameArray forKey:#"My Key"];
// for testing log names listed in nsuserdefaults
NSLog(#"%#", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);
}
Here is a link to the pastebin of the whole file contents (with comments): http://pastebin.com/hQRM9Azh
Every time you add, you're starting again with an empty array ([[NSMutableArray alloc] init]) – instead of starting with the existing items.
Here's some code which adds to the existing entries instead:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *babyNameArray = [[defaults stringArrayForKey:#"My Key"] mutableCopy];
if (!babyNameArray) babyNameArray = [[NSMutableArray alloc] init];
[babyNameArray addObject:babyname.text];
[defaults setObject:babyNameArray forKey:#"My Key"];
[defaults synchronize];
A couple of things before we start addressing the big issue. First, you'll want to have those arrays of names stored somewhere in a file(s), which you'll read either when the application starts, or in ViewWillAppear/ViewWillLoad.
Second, the way you have things working right now, you will only save one name at a time. You alloc & init a new (empty) array every time the user clicks the "Save Name" button. You then add the current name to this (empty) array and set it as the object for key "My Key." Note that your "My Key" object will always have only one element - the most recently saved name.
And lastly, you never actually save your changes to NSUserDefaults. If I recall correctly, after you are done making changes to it, you need to call synchronize - otherwise your changes will be lost as soon as the application closes. Which kind of kills the whole purpose of using data persistance. :)

Saving data to storage

Suppose I have some data I want to post to a server. You're in a deep, deep underground cellar (or you use t-mobile) and you have no internet connection. Where does that data go? Just leave it there and wait for a connection? I would rather save that data, and make it available for upload the second you have a connection.
Would using SQlite be the best way of doing this? It's just a couple of objects i want to temporarily store.
If it's only a couple of objects either save the data in NSUserDefaults or in a plist file.
NSUserDefaults set:
[[NSUserDefaults standardUserDefaults] setObject:savedStr forKey:objectDataStoreName];
[[NSUserDefaults standardUserDefaults] synchronize];
NSUserDefaults get:
savedStr = [[NSUserDefaults standardUserDefaults] objectForKey:objectDataStoreName];
or use a plist:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *newDirectory = [NSString stringWithFormat:#"%#/savedData", [paths objectAtIndex:0]];
[[NSFileManager defaultManager] createDirectoryAtPath:newDirectory withIntermediateDirectories:YES attributes:nil error:nil];
NSString *fullFileName = [NSString stringWithFormat:#"%#/savedItemsFile", newDirectory];
savedItems = [[NSMutableArray alloc] initWithContentsOfFile:fullFileName];
if (!savedItems) {
savedItems = [[NSMutableArray alloc] init];
}
savedItem = [[NSMutableDictionary alloc] init];
[savedItem setObject:savedStr forKey:objectDataStoreName];
[savedItems addObject:savedItem];
[savedItems writeToFile:fullFileName atomically:NO];
Well that is up to you, apple specifies you need to check if there is an internet connection available. And you will receive error's if you request fails.
If you can send the data later just save it and retry later. If you can't send the data on a later date then inform the user that the data could not be send.
It all depends on the specifications of the app.
I've uses plist, SQLite and coredata to keep data on the device and send them to the server when a network connection becomes available.
If you all ready store the data in database just add a bit which tells you if you have send the data to a server, or beter yet get the server to return you an identifier for the upload so you may update some record later on.
If it is not something big you can use NSUserDefaults
If it is something big I recommend you to use Core Data

Write/Read from property list on iPhone

I know there are thousands of tutorials on the topic however I don't get it to work.
What I am trying to do is to read an value from a plist file. Then I check whether or not I have to update the value. This part is ok. However I don't managed to get the value saved in plist file. I have a Settings.Bundle and it is where I am trying to read/store.
It is just a simple string.
Thanks in advance
The following will give you a NSDictionary with your settings bundle information.
[[NSBundle mainBundle] infoDictionary]
If there are any other generic plist you would like to read you can easily load them into a NSDictionary using
[NSDictionary dictionaryWithContentsOfFile:filePath];
//or
[[NSDictionary alloc] initWithContentsOfFile:filePath]
Also I would like to add for reading and saving values I would recommend doing that in the NSUserDefaults
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
id anObj = [userDefaults objectForKey:#"myKey"];
//Modify anObj
...
[userDefaults setObject:anObj forKey:#"myKey"];
[userDefaults synchronize];//Save immediately if you choose
You cannot modify files/folders present in your app bundle (such as Settings.bundle.) They are read-only.

Is it possible to read the structure of plist file?

I know that NSUserDefaults can read the key and value from plist file.
Is it possible to read the structure of a key?
For example:
Key 'Count' is an integer and has option
1,2,3,4,5
the codes below can get the value 'Count'
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
if([defaults objectForKey:#"Count"]!=nil)
{
NSString *s=[[NSString alloc] initWithString: [defaults objectForKey:#"Count"]];
NSInteger v=[s intValue];
[s release];
}
But I prefer to get all options and store to a NSArray or a better storage structure.
Is it Possible?
I read bundle settings the following way:
// Get path to Root.plist file in settings bundle and retrieving its contents
NSString* tPath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"bundle"];
settingsBundle = [[NSBundle bundleWithPath:tPath] retain];
NSDictionary* tSetDict = [NSDictionary dictionaryWithContentsOfFile:[tPath stringByAppendingPathComponent:#"Root.plist"]];
// Get array of preference dictionaries
NSArray* prefs = [tSetDict objectForKey:#"PreferenceSpecifiers"];
// Iterate through dictionaries to find required value
for (NSDictionary* setDict in prefs){
NSString* type = [setDict objectForKey:#"Type"];
if (![type isEqualToString:#"PSMultiValueSpecifier"]){
// Get possible preference values for PSMultiValueSpecifier case
// You may need to know value type in advance - not sure about that
NSArray* values = [setDict objectForKey:#"Values"];
}
}
I'm not exactly sure I understand your question but you can't directly access the user defaults system save through its defined methods. You can't read them out in one big chunk.
The defaults system isn't actually a means of reading plist files, its an more of an API for accessing a database maintained by the OS itself. Although, you don't see it much on the iPhone, its actually a very large and complex system for managing preferences not only for individual apps but also for users, groups of users, computers and networks. It seems trivial on iOS because you don't have the flexibility of configuration and use that you have on MacOS proper.
It would be impossible to read out the entire defaults because they are huge and much larger than you would expect even on iOS.
Instead of jumping through the hoops in the code you showed in the answer, you should access the data in the key using one of the dedicated methods for the type of data stored. In this case:
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
NSInteger v=[defaults integerForKey:#"Count"];
Even if you did read the defaults out in one chunk, you would just find yourself using the same type of calls and code to access the data in the alternate data structure as you would in using the defaults in the first place. You might as well use the defaults system.