iphone app memory management in save/restore - objective-c

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];

Related

Storing dictionary of dictionaries in NSUserDefaults

I am trying to store a dictionary which contains two dictionaries in NSUserDefaults.
NSUserDefaults *def = [NSUserDefaults standardUserDefaults];
NSDictionary *defDict = [def dictionaryRepresentation];
defaultTopics = [[NSArray alloc] initWithObjects:#"Animals",
#"Numbers",
#"AroundTown",
#"Actions", nil];
defaultValues = [[NSArray alloc] initWithObjects:#1, #0, #0, #0, nil];
NSDictionary *dd = [[NSDictionary alloc] initWithObjects:defaultValues forKeys:defaultTopics];
NSDictionary *de = [[NSDictionary alloc] initWithObjects:#[dd, dd] forKeys:#[#"English", #"Indonesian"]];
[defDict setValue:de forKey:#"UnlockedTopics"];
[def synchronize];
After this, NSLog(#"%#", [def dictionaryRepresentation]);, prints the correct dictionary, as long as I don't quit the app everything is as expected. But then, if I quit the app and relaunch,
[defDict objectForKey:#"UnlockedTopics"]
is always nil. What is the reason, and how to fix this?
The reason the dictionary of dictionaries is not getting stored is that you are never storing it. All you're doing is setting the key of a dictionary. You are calling
[defDict setValue:de forKey:#"UnlockedTopics"];
But that is not the same thing as setting any #"UnlockedTopics" key-value pair in NSUserDefaults. If you want to write to NSUserDefaults, write to it - e.g. by sending setObject:forKey: to NSUserDefaults.

Xcode NSMutableArray not populating data from NSUserDefaults

I have a problem with adding data to an NSMutableArray. I have a tableView and I store the cell text in an NSMutableArray. The tableView is user-populated, meaning that the user enters in what they would like to put on the tableView (to make lists, etc.). The problem I have is with storing the data they have entered. Here is my saving code:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:self.tasks forKey:#"tasksArray"];
[defaults synchronize];
The variable "tasks" is the NSMutableArray. The above code happens right after the user presses the done button on the keyboard and the keyboard text is added to the "tasks" NSMutableArray. There does not seem to be any problem here because I have debugged and found that everything saves properly.
In my viewDidLoad:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *array = [[NSArray alloc] initWithArray:[defaults objectForKey:#"tasksArray"]];
if ([self.tasks count] == 0) {
NSLog(#"Array is 0");
self.tasks = [[NSMutableArray alloc] init];
}
else {
NSLog(#"Array is > 0");
self.tasks = [[NSMutable array] alloc] initWithArray:[defaults objectForKey:#"tasksArray"]];
}
Now the above code is where I want the data to be loaded onto the array which populates the tableView. The error I get is a NSRangeException, index 0 beyond bounds of empty array. The problem seems to be with the NSMutableArray "tasks" and trying to fill it with the saved data. My goal is to have the "tasks" array populated with the data stored on NSUserDefaults under the key "tasksArray" if the user has previously entered any data. If not, and the tableView should be empty, then I want the "tasks" array to be created.
If any help could be given as to what is wrong with my code or provide a different method of getting a result that would help a lot. Thank you!
Why you are using these If else condition. Just use
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if(!self.tasks)
self.tasks = [[NSMutableArray alloc] initWithArray:[defaults objectForKey:#"tasksArray"]];
else
[self.tasks addObjectsFromArray:[defaults objectForKey:#"tasksArray"]];
Now you can place the check, where you want
if([self.tasks count]){
}
else{
}
objectForKey returns a generic object. You know it's an array, but NSMutableArray does not recognize it as such. Use either:
self.tasks = [[NSMutableArray alloc] initWithArray:[defaults arrayForKey:#"tasksArray"]];
or
self.tasks = [[NSMutableArray alloc] initWithArray:(NSArray *)[defaults objectForKey:#"tasksArray"]];

NSUserDefaults and storing values

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.

Working with NSMutableArray and NSUserDefaults

i`m trying to make an NSMutableArray From NSUserDefaults so i can add/delete and edit it later
my code is
- (void)viewDidLoad {
[super viewDidLoad];
NSUserDefaults *ArrayTable = [NSUserDefaults standardUserDefaults];
[ArrayTable setObject:#"One" forKey:#"myArray"];
[ArrayTable setObject:#"Two" forKey:#"myArray"];
[ArrayTable setObject:#"Three" forKey:#"myArray"];
[ArrayTable setObject:#"Four" forKey:#"myArray"];
[ArrayTable setObject:#"Five" forKey:#"myArray"];
[ArrayTable synchronize];
NSMutableArray *array = [[NSMutableArray alloc] init];
array = [ArrayTable objectForKey:#"myArray"];
}
- (void)viewDidUnload
{
[super viewDidUnload];
}
#pragma mark - Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [array count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
cell.textLabel.text = [array objectAtIndex:indexPath.row];
return cell;
}
when i build and run nothing shows up
i googled it but with no help, i`m sure i didn't understand how to do it
what i need is to build an app that contain a tableview with empty data, the use will fill in the data Add/Delete/Edit
can someone please explain it for me
thank you in advance
First, you assigned your array from the user defaults to a local variable named array. Assuming you have a property for this class also named array, this local assignment masks that. If it had not, you would have crashed when you tried to call -count on a string.
The NSUserDefaults object is a dictionary. Each time you call -setObject:forKey: on it, you are actually replacing the object previously set for that key. So at the end of your series of calls to -setObject:forKey:, the resulting value is the NSString Five.
You can't really store a mutable object in the NSUserDefaults, instead you would take a mutable copy of the object when you assign it to your local variable or ivar. To get the behavior you are probably expecting, you should do something like the following:
- (void)viewDidLoad
{
// Create the array template and store it in NSUserDefaults
NSArray* arrayTemplate = [NSArray arrayWithObjects:#"One", #"Two", #"Three", #"Four", #"Five", nil];
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:arrayTemplate forKey:#"myArray"];
[defaults synchronize];
// Retrieve a mutable copy of the array from user defaults and assign it to the
// the property 'array' <- note, this should be an NSMutableArray
self.array = [[defaults objectForKey:#"myArray"] mutableCopy]; // if not using arc, autorelease this here
}
With that example code, it should behave the way you expected it to behave, and you can continue on. Obviously it makes no sense to set the array in -viewDidLoad and then immediately read a mutable copy. The key thing to keep in mind is that -setObject:forKey: will always replace any object already set for that key. It doesn't add elements or anything like that.
Your problem is that you are setting a bunch of strings all to the same key:
[ArrayTable setObject:#"yourvalue" forKey:#"myArray"];
just keeps overriding your last value. In order to save an array to your defaults you will have to go about it another way. This question may help you: array to defaults
Since you don't actually want anything to do with the contents of NSUserDefaults, you're just asking how to initialize a mutable array. Here's how to do what you were trying to do:
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:#"One"];
[array addObject:#"Two"];
[array addObject:#"Three"];
[array addObject:#"Four"];
[array addObject:#"Five"];
Or, more simply:
NSMutableArray *array = [NSMutableArray arrayWithObjects:#"One", #"Two", #"Three", #"Four", #"Five", nil];
(These aren't quite identical. If you're using MRC, the first creates an array that you own and will have to release manually, while the second creates an array that's autoreleased. If you don't understand the difference, the second is better until you learn, but soon you should go learn.)
If you want to know why what you were doing didn't work, here goes:
NSUserDefaults *ArrayTable = [NSUserDefaults standardUserDefaults];
Here you're copying an NSUserDefaults object, which is not an array, or a table; it's a wrapper around the user and system preferences that acts like a dictionary with extra functionality.
[ArrayTable setObject:#"One" forKey:#"myArray"];
This line adds a preference named "myArray" to the user-domain preferences for your application, with the value "One".
[ArrayTable setObject:#"Two" forKey:#"myArray"];
[ArrayTable setObject:#"Three" forKey:#"myArray"];
[ArrayTable setObject:#"Four" forKey:#"myArray"];
[ArrayTable setObject:#"Five" forKey:#"myArray"];
These lines change the value of your "myArray" preference repeatedly, ending with "Five".
[ArrayTable synchronize];
This makes sure that your preference is saved to disk.
NSMutableArray *array = [[NSMutableArray alloc] init];
This creates a new mutable array and stores it in the variable "array".
array = [ArrayTable objectForKey:#"myArray"];
This gets the "five" string out of your preferences and stores it into the variable "array".
This means you've now lost the only reference you had to the actual array.
This also means the static type of the variable no longer matches the dynamic type. When you later call [array count], or [array objectAtIndex:n], you're sending those messages to a string, not an array, so you're probably going to get an exception or other unexpected behavior. (Well, it's pretty much guaranteed that whatever you get is going to be unexpected, since you thought you were talking to an array of 5 objects, not a string.)
Create the array and add that to user defaults.
- (void)viewDidLoad {
//...
NSArray * tmpArray = [NSArray arrayWithObjects:#"One",#"Two",#"Three",#"Four", #"Five", nil];
NSUserDefaults *ArrayTable = [NSUserDefaults standardUserDefaults];
[ArrayTable setObject:tmpArray forKey:#"myArray"];
[ArrayTable synchronize];
array = [[ArrayTable objectForKey:#"myArray"] mutableCopy];
// array has to be an ivar of your viewcontroller to access it outside of viewDidLoad
// define in your header like #property (nonatomic, strong) NSMutableArray * array;
// with #synthesize array; in the implementation
}

Saving MKOverlayView array -> plist

I have an application that involves lots of MKOverlayViews. Every time one is added to the map, it is also added to an NSArray. When the user wishes to stop adding overlays, I would like for them to be able to save them, and be able to access them later. How can I store an array of MKOverlayViews into a plist, and then reload them back into a mapView later? Is this possible?
I'm attempting to use this code to take the MKPolylineViews from the array and add the corresponding MKPolylines to the map, but it crashes at the '[mapView addOverlay....' line.
Writing array to plist:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if(overlays)
{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:overlays];
[defaults setObject:data forKey:#"theKey"];
[defaults synchronize];
}
Reading data from plist:
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"theKey"];
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
for(MKPolylineView* a in arr)
[mapView addOverlay:a.polyline];
I'd look at archiving with NSArchiver or NSKeyedArchiver and then writing them out to disk. Probably better than trying to squeeze them into a plist somehow...