inconsistant defaults (NSUserDefaults) behavior in iPhone simulator defaults - iphone

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.

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 to store array of integer between app launches?

I want to store some data as a matrix of integers. For simplicity let's say I want to store this data in an array of integers. After each app launch I want to fill my model using this array. So the question is How to store arrays of integer in sandbox?
I don't want to write and rewrite data, maybe only once at first start.
What I tried:
I know about storing in plists and storing using NSCoding. But this strategies are used for more complicated models, not just arrays of integers.
EDIT:
is it faster to use c-arrays, storing them in txt-files and making own parser?
Plists and NSCoding are used for the simple integers as well.
However, you could just use the NSUserDefaults - that is the simples way:
[[NSUserDefaults standardUserDefaults] setObject:myArray forKey:#"AppData"];
Makes it easy to retrieve later from anywhere as well.
You can either store it in a plist, as you said, it's not a bad idea, it would work and do the job. It's not complicated at all, really, and not only used for complex models.
Else, you might want to use NSUserDefaults :
if (![NSUserDefaults standardUserDefaults] objectForKey:#"AKey"]) {
[[NSUserDefaults standardUserDefaults] setObject:yourArray forKey:#"AKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
Don't forget that you need to wrap your integers into NSNumber.
A good practice for that would be using Objective-C's "new" notation : NSNumber * nb = #(42). That is much more readable that [NSNumber numberWithInt:42].
Good luck !
EDIT : According to your edit, no, don't use your own parser, at least for now. Don't try to optimize code when you don't really need it. Especially it it involves "breaking" Objective-C standards (and by that I mean using your own made stuff that might bug, and/or introduce strange behavior where Objective-C provides it's own way to do it). See this answer to know more about too much optimization.
+(void)storeArrayInDeviceWithKey:(NSArray *)arrData withKey:(NSString *)key{
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
[currentDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:arrData] forKey:key];
[currentDefaults synchronize];
}
For NSArray you need to little conversation with
NSKeyedArchiver archivedDataWithRootObject:UR OBJECT

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.

NSUserDefaults: Question about difference between two approaches

Problem
I want to store a NSString in NSUserDefaults and retrieve it later. I have a question about two different retrieving methods. Now at the top of the file I have:
// String used to identify the update object in the user defaults storage.
static NSString * const kLastStoreUpdateKey = #"LastStoreUpdate";
Method 1
NSString *lastUpdate = [[NSUserDefaults standardUserDefaults] objectForKey:kLastStoreUpdateKey];
Method 2
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSString *myString = [prefs stringForKey:kLastStoreUpdateKey];
Are there are significant differences I should know about? Also, can someone please explain what exactly is objectForKey? Apple's API states: that it "Returns the object associated with the first occurrence of the specified default." What exactly do they mean by the "specified default?
Thank you!
Generally you should use method 1.
that is "objectForKey".
Because, you know that, whatever you have stored in NSUserDefault. So, at the time of retriving it, you can catch the object with proper class like NSString, Array or any other user defined.
genrally "stringForKey" is not used.
If you are storing ingteger, BOOL into NSUserDefault then you should use intForKey, BOOLforKey, etc..
Cheers.

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.