Deleting files archived with NSCoding - iphone

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).

Related

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

Using PLists for Persistence on iPhone

Simple question about property lists within an iphone app. I know you can read data in from a plist, but is there a way to write user-inputted data to a plist? If so, how? It's easy to find tutorials on reading information from plists, but I'm having trouble finding resources on writing to plists.
This is how I write data items to a plist:
[myPlistFile setInteger: myInt forKey: #"someKey"];
Of course, you can change setInteger with setBool, etc for different types.
Hope this helps!
--
Edit:
If your .plist was a member of an important class or similar...
Header of myClass:
NSUserDefaults* myPreferences;
#property (nonatomic, retain) NSUserDefaults* myPreferences;
.m of myClass:
self.myPreferences = [NSUserDefaults standardUserDefaults]; // load our preferences
[[NSUserDefaults standardUserDefaults] registerDefaults: [NSDictionary dictionaryWithContentsOfFile: [[NSBundle mainBundle]pathForResource: #"nameOfFile" ofType: #"plist"]]]; // now load the custom .plist file
In the docs for both NSArray and NSDictionary it shows they each have an instance method:
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
For NSDictionary it describes this method as
Writes a property list representation of the contents of the dictionary to a given path.
For NSArray it says this in the discussion
This method recursively validates that all the contained objects are property list objects 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 essentially both of these will write plist's if the items that they contain can be used in plists e.g. Array, Dictionary, Boolean, Data, Date, Number and String

How to easily persist application state upon exit

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.

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.

inconsistant defaults (NSUserDefaults) behavior in iPhone simulator defaults

I've been experiencing very inconsistent results while developing an iPhone app and trying to save preferences via the standard NSUserDefaults mechanism. I am using code almost straight out of the iPhone Developer's Cookbook by Erica Sadun (fantastic book btw), it looks like this:
(void) updateDefaults
{
NSMutableArray *spells = [[NSMutableArray alloc] init];
NSMutableArray *locs = [[NSMutableArray alloc] init];
for (DragView *dv in [boardView subviews])
{
[spells addObject:[dv whichSpell]];
[locs addObject:NSStringFromCGRect([dv frame])];
}
[[NSUserDefaults standardUserDefaults] setObject:spells forKey:#"spells"];
[[NSUserDefaults standardUserDefaults] setObject:locs forKey:#"locs"];
[[NSUserDefaults standardUserDefaults] synchronize];
[spells release];
[locs release];
}
The values are saved, sometimes...and restored, sometimes. I can't get an exact bead on what does or does not make it work.
Does anyone else have any similar experiences? Any suggestions on what might make it work? Is the synchronize method the best way to force a disk write and make the values save, or is there something better (both for production, as well as simulator).
Thanks
Ryan
You should be using an NSKeyedArchiver for saving your arrays, such as:
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:spells] forKey:#"spells"];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:locs] forKey:#"locs"];
[[NSUserDefaults standardUserDefaults] synchronize];
You should also make sure your spells class implements the NSCoding protocol (encodeWithCoder: and initWithCoder:), if it's a custom class. It looks like your locs are NSStrings, which will archive just fine.
You'll also need to do something like
NSData *dataRepresentingSavedSpells = [currentDefaults objectForKey:#"spells"];
if (dataRepresentingSavedSpells != nil)
{
NSArray *oldSpells = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedSpells];
}
To load the old values from the defaults.
I use synchronize to write to disk on exit, and it's been very reliable in my experience.
On Mac OS X, and probably also on iPhone OS, the defaults database can only contain property list objects: NSString, NSNumber, NSArray, NSDictionary, and NSData.
It's not clear from your code snippet what type a spell is, but if it isn't one of the above, it won't get stored in the defaults database. Your options are:
Write a method that will convert your object to and from a plist representation. In other words, a method to create an NSDictionary from your object and another method to initialize the object using an NSDictionary. You could also choose to store it as an NSArray or NSString.
Make your object implement the NSCoding protocol and then used NSKeyedArchiver to convert the object to an NSData object, and NSKeyedUnarchiver to convert it back.