How to easily persist application state upon exit - iphone

Let's say that my application state is extracted into an object (so that all information specific to app instance is contained in one object) and that object supports nscoding protocol. How can i easily persist it and load it on exit/launch of my application?
My current code looks like this
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
State* state = (State*)[defaults objectForKey:#"State"];
if(state!=nil)
{
viewController.state = state;
}
}
- (void)applicationWillTerminate:(UIApplication *)application {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:self.viewController.state forKey:#"State"];
}
But upon loading state is always nil... so I assume it is not the best pattern out there :)
It turns out that NSUserDefaults supports only Property List objects such as NSArray, NSData, etc... no custom objects unless you wrap it in nsdata

There are a number of ways of storing the state of your application before it exits and restoring it when it launches again. The best approach really depends on your application.
If you have a simple object model with a single root object and you tend to need the entire object model in memory at once, then you may find the NSCoding protocol (see link below) a good way of saving state.
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Protocols/NSCoding_Protocol/Reference/Reference.html
Any of the objects you want to persist should implement this protocol's two methods:
- (void)encodeWithCoder:(NSCoder *)aCoder // to serialise the object
- (id)initWithCoder:(NSCoder *)aCoder // to restore an object
Once you've implemented these methods in any of the objects you want to persist, you can serialise your object graph with something like this:
- (void)applicationWillTerminate:(UIApplication *)application {
[NSKeyedArchiver archiveRootObject:yourRootObject toFile:someFilePath];
}
When you want to restore the archived objects:
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
id yourRootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:someFilePath];
}
Alternatively, if your application's object model is slightly more complex you might consider using Core Data.
http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreData/cdProgrammingGuide.html
Finally, whilst the user defaults system can be used for storing and retrieving values, it is best used for simple application settings (or preferences). It isn't really intended for saving your entire application's state.

What type of object is state?
I believe that NSUserDefaults will only save NSData, NSString, NSDictionary, NSDate, NSArray and NSNumber.
In the past, I have saved the state into a dictionary and stored it using NSUserDefaults. You could also archive your state and convert it to NSData and save that with NSUserDefaults.

Related

Deleting files archived with NSCoding

I'm trying to delete all files that my application has persisted using NSCoding. Seems like it should be a simple thing to do, but I can't find an answer, whilst I'm showing just one class here, I have several and I'd like the delete function to save all instances of all classes my app has archived. I'm not doing anything different to NSCoder:
So my interface is defined
Details : NSObject <NSCoding>
My implementation has:
- (id)initWithCoder:(NSCoder *)decoder
{
NSLog(#"initWithCoder 'Details'");
if (self = [super init])
{
NSLog(#"Decoding 'Details'");
self.name = [decoder decodeObjectForKey:#"name"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
NSLog(#"Encoding 'Details'");
[encoder encodeObject:self.name forKey:#"name"];
}
When I write it I'm using:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object];
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:data forKey:identifier];
I did start off trying to look in the documents directory, but I think as I'm writing this and from my research that it will be written to NSUserDefaults (and that might not be the best thing). So at the risk of asking two questions, how do I delete all my objects that are saved/archived using NSCoder to NSUserDefaults (and potentially, should I be setting this to something else (i.e a sub-folder inside the documents folder....if yes, how would I do that).
NSCoding provides a way to transform objects into NSData for easy archiving. What you do with those NSData objects afterward are your responsibility. You may or may not save them to individual files, but NSCoding is not aware of that.
It seems like you have been dumping those NSData into NSUserDefaults using [userDefaults setObject:data forKey:identifier];
You need to delete all entries for every identifier you have used by doing [userDefaults removeObjectForKey:identifier];.
You can't automatically determine what those entries are.
Additionally, you might want to consider not hogging NSUserDefaults and save those to individual files. (look at [NSData writeToFile:]) This will additionally help you track what those files are (you could for example all place them in the same folder/subfolder).

How do you store multiple UIControlState instances in NSUserDefaults?

I'm looking to storing multiple instances of UIControlState within NSUserDefaults on the IOS platform.
Let's say there is a preference panel with various UIControl elements, be they UISlider, UISwitch etc.
Currently I have a loadPrefs function within viewWillAppear and a corresponding writePrefs within viewWillDisappear.
Rather than adding the code to store the state within NSUserDefaults for any future control that I add, is there an easy way to iterate through all the keys in NSUserDefaults and set the relevant states accordingly?
Is there a "proper" way of doing this via an NSArray or some other collection or is it not worth the time and bother?
You can get the entire NSUserDefaults stuff as an NSDictionary by using the dictionaryRepresentation method:
NSDictionary *dict = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
for (NSString *key in [dict allKeys])
{
// do whatever you want
}
Hope this helps.

Storing complete class object into database in iPhone

I am new to this development.
I want to store complete object of a class into my database.
Actually I am creating application where user can add multiple views to parent view and want to save it, so that next time when user fetches it, he will get what ever he has saved i.e. views to parent view previously.
Any logic or suggestion on same will really be helpful,
Thanks in advance
Any object you want to save will need to conform to the NSCoder protocol. Keep in mind that if you have custom objects within your parent object that they to will need to conform to NSCoder. Add the following methods to your custom class(es).
- (id)initWithCoder:(NSCoder *)coder {
_inventory = [[coder decodeObjectForKey:#"inventory"] retain];
return self;
}
- (void)encodeWithCoder:(NSCoder *) encoder{
[encoder encodeObject:_inventory forKey:#"inventory"];
}
In the example above I want to encode a player's inventory. If the inventory contains custom objects (as opposed to a bunch of NSStrings for example) they'll also need their own NSCoder methods.
Below I turn it into something you can save out to NSUserDefaults. Adjust appropriately to store in a DB. Keep in mind if you want to send NSData over the wire to store in a DB you'll want to convert it to Base64 and possibly compress it.
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData * encodedObject = [NSKeyedArchiver archivedDataWithRootObject:rootObject];
[defaults setObject:encodedObject forKey:kSaveArchiveKey];
[defaults synchronize];
To go the other way, you'll want to grab your NSData, do whatever magic on it as I described above (base64, compression) and unarchive.
PlayerInventory *inventory = [NSKeyedUnarchiver unarchiveObjectWithData:playerInventoryData]
You should choose between NSCoding and Core Data depending on your exact needs. See this post for more info: NSCoding VS Core data
You can store state(value of any attributes) of any object in database. You should use NSCoding.Example here

What's the best way to get a value from NSUserDefaults on app load and store it in a global variable?

What's the best way to get a value from NSUserDefaults on app load and store it in a global variable?
I could just hit [[NSUserDefaults standardUserDefaults] objectForKey:#"Theme"] every time I wanted to access the value stored, but it would hit the disk every time and that would be bad (I need the value for UITableView cells). What I'd like to do is store that value from it to a global variable on load, and then use that variable throughout the app.
What's the best way to do this? Obviously I can't make a constant because [[NSUserDefaults standardUserDefaults] objectForKey:#"Theme"] can't be compiled as a constant. How should I make a global variable for this? Is there any way to put it in the main.m or Prefix.pch file? I would hate to have to hit the App Delegate every time throughout my whole app.
"NSUserDefaults caches the information to avoid having to open the user’s defaults database each time you need a default value." - NSUserDefaults Class Reference
However, if you really want to have this 'constant' (it won't be a true constant though) do something like this
*.pch:
extern NSString *themeString;
AppDelegate.m:
NSString *themeString = nil;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// ...
themeString = [[[NSUserDefaults standardUserDefaults] objectForKey:#"Theme"] copy];
// ...
}

Load data into core-data schema

I am implementing a navigation-based application. The user will drill down to the node of interest. I am using Core Data, mostly because I want to try it out. How do I load the database initially? Should I write custom code that loads the database initially, or is there some tool to do it for me ?
Here's a simple way to preload the Core Data store using plists.
Make a property list containing an array of dictionaries. Make the keys of each dictionary correspond to the keys of your managed object.
Then, call this method the first time the app launches:
- (void)loadDataFromPropertyList {
NSString *path = [[NSBundle mainBundle] pathForResource:#"someFile" ofType:#"plist"];
NSArray *items = [NSArray arrayWithContentsOfFile:path];
NSManagedObjectContext *ctx = self.managedObjectContext;
for (NSDictionary *dict in items) {
NSManagedObject *m = [NSEntityDescription insertNewObjectForEntityForName:#"TheNameOfYourEntity" inManagedObjectContext:ctx];
[m setValuesForKeysWithDictionary:dict];
}
NSError *err = nil;
[ctx save:&err];
if (err != nil) {
NSLog(#"error saving managed object context: %#", err);
}
}
Call loadDataFromPropertyList the first time the app launches by including the following code in the implementation of application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if (![defaults objectForKey:#"firstRun"])
{
[defaults setObject:[NSDate date] forKey:#"firstRun"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self loadDataFromPropertyList];
}
There is no automatic (i.e. built-in) method for importing data into a Core Data context.
I suspect that this is because Core Data is really an object graph management framework (that just happens to be able to persist that object graph to disk) and the mapping between data and object instances depends on the schema (and so will require at least some code).
If you already have the data in another format, you should read the section of the Core Data Programming Guide on importing data into a Core Data context. If you don't already have the data in an other format, you will have to write code either way (to generate an intermediate format or to populate the context directly).
Finally, although it is not really a public API, the Core Data XML format is pretty easy to work with by hand or using any number of XML-based tools. Depending on the complexity of your data, you may be able to reverse-engineer the XML schema enough to generate an XML-backed persistent store. You could then migrate this store to an SQLite persistent store and you're on your way.
Currently as far as I know you have to write custom code to populate the database.
This does seem like a downside, I'd really like to see a tool for batch (shell) pre-population of data sources meant to be used by CoreData.