NSUserDefaults and storing values - objective-c

Hi I am trying to store an array into NSUserDefaults but I am having troubles. The method accepts an NSDictionary which I will store into an array that i will store into NSUSerDefaults. The problem is when I make a mutableCopy it says its a dictionary and not of type NSMutable array? This method is the first time I would be calling NSUserDefaults so I am unsure why the error is happening? Here is the code thanks
+(void) getRecentPhoto:(NSDictionary *)recentPhoto{
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
//stores it as a dictionary? error happens here
NSMutableArray* recentPhotos = [[defaults objectForKey:#"recentPhoto"] mutableCopy];
NSLog(#"%#", [recentPhotos class]);
if(!recentPhotos) recentPhotos = [NSMutableArray array];
BOOL copy = NO;
//these will crash the program
NSLog(#"%#", [[recentPhotos objectAtIndex:0] objectForKey:#"id"]);
NSLog(#"%#", [recentPhoto objectForKey:#"id"]);
//this checks if it has been stored before by using an id key
for(int i =0; i < [recentPhotos count]; i++){
if ([[[recentPhotos objectAtIndex:i] objectForKey:#"id"] isEqualToString:[recentPhoto objectForKey:#"id"]] ) {
copy = YES;
}
}
if(copy ==NO)
[recentPhotos addObject:recentPhoto];
[defaults setObject:recentPhoto forKey:#"recentPhoto"];
[defaults synchronize];
}
This is the error
NSInvalidArgumentException', reason: '-[__NSCFDictionary objectAtIndex:]: unrecognized selector

I believe the problem is, in the end of this method, you try to store recentPhoto, which is a dictionary, into user default instead of recentPhotos, the mutable array you want to store.
Actually, I think it will not crash at the first time this method is called since recentPhoto has not been stored in user default. But after that, it will.

Related

adding objects crash in ios

i am trying to add an object into a nsuserdefault, but i get this crash
"[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object"
its crashing on this line:
[currentFav addObject:incomingBabe];
I have no idea why its crashing, its working on my other project.
here is my code
-(IBAction)favorite {
NSUserDefaults *standardDefault = [NSUserDefaults standardUserDefaults];
NSMutableArray *currentFav = [[NSUserDefaults standardUserDefaults]objectForKey:#"fav"];
NSLog(#"strings stored = %#",currentFav);
NSMutableArray *newFav = [NSMutableArray arrayWithObject:[NSString stringWithFormat:#"bikini%02d.jpeg",self.currentNumber]];
if (currentFav == NULL){
currentFav = [[NSMutableArray alloc]init];
}
for(NSString *incomingBabe in newFav){
BOOL hasStringAlready = NO;
for(NSString *currentFavorite in currentFav){
if([currentFavorite isEqualToString:incomingBabe]){
hasStringAlready = YES;
NSLog(#"has string already");
break;
}
}
if (!hasStringAlready) {
[currentFav addObject:incomingBabe];
hasStringAlready = YES;
}
}
[standardDefault setObject:currentFav forKey:#"fav"];
[standardDefault synchronize];
}
Basically it says you are trying to use a method from NSMutableArray on NSArray.
This is because "Values returned from NSUserDefaults are immutable, even if you set a mutable object as the value."
NSMutableArray *currentFav = [[NSUserDefaults standardUserDefaults]objectForKey:#"fav"];
will return an array, not mutable array. You should make a mutable copy of it.
NSMutableArray *currentFav = [[[NSUserDefaults standardUserDefaults]objectForKey:#"fav"] mutableCopy];
You can get the reason of your problem from 1 floor. You can use his method to solve your problem, or like this:
NSMutableArray *currentFav = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults]objectForKey:#"fav"]];

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: NSArrayM objectForKey

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM objectForKey:]: unrecognized selector sent to instance 0x12e654c0.
I am loading data on tableview from Array that has two dictionary objects. Dictionary object contains 2 nsstring object when ViewDidLoad called the code is below
while (sqlite3_step(statement) == SQLITE_ROW) {
NSString* delID = [NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, 0)];
NSString* name =[NSString stringWithUTF8String:(const char *) sqlite3_column_text(statement, 1)];
NSMutableDictionary *dict=[NSMutableDictionary dictionary];
[dict setObject:delID forKey:#"delID"];
[dict setObject:name forKey:#"name"];
[self.arr addObject:dict];
It never crash for first row but on second row indexPath.row==1 it always crash please see the screen shot below. Thanks for help
Here is I am filling self.arr again
-(void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
if ([datePicker1.CurrentDate length]) {
UIButton *btn=(UIButton*)[self.view viewWithTag:btnTag];
[btn setTitle:datePicker1.CurrentDate forState:UIControlStateNormal];
}
if ([self.searchDel.arrSelectDelAdd count ])
{
[self.arr addObject:self.searchDel.arrSelectDelAdd];
[self.tblDelivery reloadData];
}
}
Remove the if condition from your code. As it is just adding first dictionary into the array not the second one.
//if (i==0){
[self.arr addObject:dict];
//}
Try this :)
Sorry to bother all of you Actually the problem is that I am adding second or another object like
[self.arr addObject:self.searchDel.arrSelectDelAdd]; //Incorrect, that is adding NSArray type not dictionary
then getting values in cellForRowAtIndexPath
NSDictionary *dict=[arr objectAtIndex:indexPath.row];//here dict will contain NSSArray type not dict type object its causing to crash app.
cell.lblID.text=(NSString*)[dict objectForKey:#"delID"];// at this line.
cell.lblName.text=(NSString*)[dict objectForKey:#"name"];
[self.arr addObjectsFromArray:self.searchDel.arrSelectDelAdd]; // correct, thats the solution giving contents of NSSArray and now working my code

iphone app memory management in save/restore

I am confused by the memory management in this scenario.
In my app user makes periodic input inside UITextField tf and the typed strings (NSString*) are stored as elements of a MSMutableArray *arr through addObject. The stored collection is displayed inside a UITableView. My app can go into bkgr and is periodically awakened by push notifications. As I understand it, the data stored in arr can be lost while my app is non-active and, to preserve it, I need to do archive/restore.
My archive/restore are using
NSUserDefaults*prefs;
[prefs setObjectForKey:x forKey:key]
to archive and
[prefs objectForKey:key]
to restore every item of arr.
Question1: I think that to prevent the memory leak I need to do [arr release]
Do I also need to do a release on every object which I have added to arr or, since I did not allocate the NSString for tf, it will be done for me automatically?
Question2: in restore I start with something like arr=[[NSMutableArray alloc] initWithObjects:nil]; before I can read and add archived items back to arr. I think that [prefs objectForKey:key] is released as soon as I leave the scope in which it was read - thus I need something like retain to keep it in arr. Would this schema work in the next archive/restore cycle due to another app deep sleep?
Is there a cleaner way of achieving the same?
Thanks.
Victor
Adding objects to an NSArray causes the NSArray to retain each object.
So in a case where you are instantiating objects, then adding them to an array, those objects do not need to be further retained:
// saving strings inside an array, then array to the NSUserDefaults
NSString *string1 = #"My String 1";
NSString *string2 = #"My String 1";
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:10];
[arr addObject:string1];
[arr addObject:string2];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];;
[prefs setObject:arr forKey:#"MyArray"];
[arr release];
Then to restore the entire array from prefs:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
NSArray *array = [prefs objectForKey:#"MyArray"];
Alternately, to save strings under separate keys, it would be something like this:
[prefs setObject:[arr objectAtIndex:0] forKey:#"MyFirstStringKey"];
[prefs setObject:[arr objectAtIndex:1] forKey:#"MySecondStringKey"];
For the restore, you will also just add the items to the array, no retain required:
// assuming this time several keys added to an array
// also note using autoreleased version of array - much easier
NSMutableArray *arr = [NSMutableArray arrayWithCapacity:10];
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];;
[arr addObject:[prefs objectForKey:#"MyFirstStringKey"]];
[arr addObject:[prefs objectForKey:#"MySecondStringKey"]];
// then assign arr or use it otherwise
Also easier still is to use a non-mutable array and instantiate the array with the list of objects you want to have on the array:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];;
NSArray *arr = [NSArray arrayWithObjects:[prefs objectForKey:#"MyFirstStringKey"], [prefs objectForKey:#"MySecondStringKey"], nil];

Problem with NSMutableDictionary setObject: forKey - crashes

I'm strugglig with the following code. I don't undesratnd what's wrong with it.
Code is commented with respect to the crash:
- (IBAction) SavePreset: (id) sender
{
NSString *presetName = [nameCombo stringValue]; // nameCombo is a NSComboBox*
NSUserDefaults *vmDefaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *projectionPresets = [vmDefaults objectForKey: kVmGeorefPresetKey];
BOOL doSave = YES;
NSString *dictEntry = [projectionPresets objectForKey: presetName];
if (dictEntry) {
// this branch is not tested yet, plan to test when the rest is working.
int userChoice;
userChoice = NSRunAlertPanel( #"Save Preset",
#"This preset (%s) already exists, modify?",
#"OK", #"Cancel", nil, presetName);
doSave = (userChoice == NSAlertDefaultReturn);
if (doSave) {
[nameCombo addItemWithObjectValue: presetName];
}
}
if (doSave)
{
// projParamText is of class NSTextField*
NSString *presetParam = [projParamText stringValue];
// Up to this point, everything works as expected
// But, the following statement crashes silently.
[projectionPresets setObject: presetParam forKey: presetName];
// and the subsequent code is never executed
[savePresetButton setEnabled: NO];
}
}
I wonder whether he NSString* returned from [NSControl stringValue] returns a pointer to an internal string reperesentaion or a new NSString that will not change if I edit the text of the NSTextField later on.
Found the culprit. The following statment is from the NSUserDefaults documentation:
Values returned from NSUserDefaults are immutable, even if you set a
mutable object as the value. For example, if you set a mutable string
as the value for "MyStringDefault", the string you later retrieve
using stringForKey: will be immutable.
The workaround is do create a new preset dictioary from the old immutable one, modify that and store it back with the user defaults.
try [projectionPresets setValue: presetParam forKey: presetName];
Also NSLog your presetName and presetParam and see if any of those are nil values.
To setobject in an NSMutableDictionary you have to first do
NSMutableDictionary *projectionPresets = [[NSMutableDictionary alloc] init];
Then only you can setobject for an NSMutableDictionary. The mutable dictionary should be an absolute mutable.

Memory errors when trying to create and populate a NSMutableDictionary

I am not a Cocoa developer, but I have been dabbling in it to build some plugins for PhoneGap. This particular plugin method is either 1) crashing the app without saying why or 2) complaining about how I release/don't release an object. I have tried a ton of things on my end, including using an Enumerator instead of the for loop. If anyone can point me in the right direction, that would be awesome. I don't mind legwork:
- (void)getPreferences:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options {
NSUInteger argc = [arguments count];
NSString* jsCallback = nil;
if (argc > 0) {
jsCallback = [arguments objectAtIndex:0];
} else {
NSLog(#"Preferences.getPreferences: Missing 1st parameter.");
return;
}
NSDictionary *defaults = [[NSUserDefaults standardUserDefaults] dictionaryRepresentation];
NSMutableArray *keys = (NSMutableArray *) [options objectForKey:#"keys"];
NSMutableDictionary *values = [[NSMutableDictionary alloc] init];
NSUInteger ky = [keys count];
for (int i = 0; i < ky; i ++) {
#try {
[values setObject:[defaults objectForKey:[keys objectAtIndex:i]] forKey:[keys objectAtIndex:i]];
}
#catch (NSException * err) {
NSLog(#"Error %#", err);
}
}
[keys release];
NSString* jsString = [[NSString alloc] initWithFormat:#"%#(%#);", jsCallback, [values JSONRepresentation]];
[defaults release];
[values release];
[webView stringByEvaluatingJavaScriptFromString:jsString];
[jsString release];
}
Human version:
options contains a dictionary with a single key of "keys"
that key contains an array of strings (that are going to be used as keys for lookup)
I want to loop through that array and
For every value that exists in defaults for that key, copy it to values using the same key
Finally, I want to send that values back as JSON (This part was working when I just passed the entire defaults object in, so I think the JSON method is working)
From your code, it follows that you 'own' objects values and jsString (the ones you created with alloc), so you should release them and not any other.
You can read more on memory management here.
Is this the whole code? Also, what exactly error do you get?
Nikita is right, it looks as though you're overreleasing defaults, which would cause a crash later when the autorelease pool gets released. Also, if I understand what you're trying to do correctly, you could create the values dictionary with a single line of code:
NSDictionary *values = [defaultsDict dictionaryWithValuesForKeys:keys];