Memory management creating an NSDictionary with NSMutableArrays - iphone

I'm having problem understanding memory management when creating a dictionary with mutable arrays. I'm using the ios6 SDK with deployment target 5.1.
In the implementation of the class "Group" the method "namesAndEmails" builds an array "emails" that contains the emails addresses for Person objects with an email. If the Person object does not have an email the Person name is added to another array "namesWithNoEmail". The arrays are returned in a dictionary.
#import "Group.h"
#implementation Group
-(NSDictionary*) namesAndEmails {
NSMutableArray *emails = [[NSMutableArray alloc] initWithCapacity:0] ;
NSMutableArray *namesWithNoEmail = [[NSMutableArray alloc] initWithCapacity:0];
NSString *email;
NSString *name;
for (Person *p in allPersons) {
email = p.email;
name = p.name;
if ([email length]==0) {
[namesWithNoEmail addObject:name];
} else {
[emails addObject:email];
}
}
NSArray *keys = [NSArray arrayWithObjects:#"emails",#"names", nil];
NSArray *objects = [NSArray arrayWithObjects:emails, namesWithNoEmail, nil];
//[emails release];
//[namesWithNoEmail release];
return [NSDictionary dictionaryWithObjects:objects forKeys:keys];
}
Somewhere else in the code I wish to send an email to a group of people so I call the emailGroup method which gets a dictionary out by calling "namesAndEmails" on the group.
-(void) emailGroup:(Group*) g {
NSDictionary *emailInfo = [g namesAndEmails];
guestsWithNoEmail = [emailInfo objectForKey:#"names"];
guestEmails = [emailInfo objectForKey:#"emails"];
int nGuestsWithNoEmail = [guestsWithNoEmail count];
if (nGuestsWithNoEmail > 0) {
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:#"No emails" message:#"" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil] autorelease];
[alert show];
}
// some more code here
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
[picker setSubject:subject];
[picker setMessageBody:#"" isHTML:NO];
[picker setToRecipients:guestEmails];
[[self delegate ] presentModalViewController:picker animated:YES];
[picker release];
}
As far as I understand [NSDictionary dictionaryWithObjects:objects forKeys:keys] in "namesAndEmails" returns an autoreleased dictionary. But why does my code crash if I release the "emails" and "namesWithNoEmail" arrays? I thought that the dictionary would have ownership of the array after they are added and therefore it would be safe to release the arrays in the method. I guess that's not correct, but why?
Is the a more clean way of doing this? Thank you for any advice!

My first suggestion would be to use the "Product->Analyze" feature. If you leaking or over releasing somewhere, it will probably give you the exact chain of events.
Secondly, I can't see the linking between your methods nameAndEmails and emailGroup:. Because I can't see the connection, I can't tell you if the autorelease is causing the problem.
Autoreleased objects get released when the the main run loop cycles. So it's very possible your NSDictionary is getting released. You could test this by doing anything from setting the memory location as a "watch" in the debugger to putting printing something in the console lines each time the runloop your in cycles (I made the assumption your in the main run loop, so correct me if that's not true).
Other things you can do to track the problem would be to use "Zombies" in instruments or NSZombieEnable=YES in your configuration

Related

Error with NSDictionary

I am trying to generate an UIActionSheet from a NSDictionary.
codeCountryMap is NSDictionary variable defined in .h file. The code compiled correctly but crashes on run time. But the whole code works when the initialization is done in the handleEvents method
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *codes = [NSArray arrayWithObjects:#"91", #"01", #"002", nil];
NSArray *cName = [NSArray arrayWithObjects:#"ABC", #"QWE", #"XYZ", nil];
codeCountryMap = [NSDictionary dictionaryWithObjects:codes forKeys:cName];
}
-(IBAction) handleEvents:(id)sender
{
UIActionSheet *displayCodeCountryMap = [[UIActionSheet alloc] initWithTitle:#"Select Country" delegate:self cancelButtonTitle:nil
destructiveButtonTitle:nil otherButtonTitles:nil,nil];
for(id key in codeCountryMap) {
[displayCodeCountryMap addButtonWithTitle:(NSString *)key];
}
[displayCodeCountryMap addButtonWithTitle:#"Cancel"];
displayCodeCountryMap.cancelButtonIndex = [codeCountryMap count];
displayCodeCountryMap.actionSheetStyle = UIActionSheetStyleBlackOpaque;
[displayCodeCountryMap showInView:self.view];
[displayCodeCountryMap release];
[country resignFirstResponder];
}
The app crashes when the handleEvents: method is called.
Any kind of help would be greatly appriciated.
Thanks in advance.
This more than likely is a memory management issue. It looks like you are setting the iVar codeCountryMap to an autoreleased object of NSDictionary and not retaining it(at least not in this code snippet). This means that when the 'handleEvents' method is called the NSDictionary item has been released from memory.
Try either retaining the NSDictionary item or, if codeCountryMap is defined as a retained property, then use the following code to set it.
self.codeContryMap = [NSDictionary dictionaryWithObjects:codes forKeys:cName];
This will use the synthesized methods to set the property and retain it.
No need for 2 nils terminators in the otherButtonTitles argument. If you had passed in strings then it would have to be nil terminated. Since you did not, one nil is enough.
UIActionSheet *displayCodeCountryMap = [[UIActionSheet alloc] initWithTitle:#"Select Country" delegate:self cancelButtonTitle:nil
destructiveButtonTitle:nil otherButtonTitles:nil];

NSURLRequest converting NSData to an array

I need to convert data received from the web via an array in a PHP script into an array that I can pull values out of. Here's my code!
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
//NSString *payloadAsString = [NSString stringWithUTF8String:[receivedData bytes]];
NSArray *payloadAsString = [NSKeyedUnarchiver unarchiveObjectWithData:receivedData];
[payloadAsString finishEncoding];
verified = [payloadAsString objectAtIndex:0];
NSLog(#"logging");
//NSString *no = [[NSString alloc] init stringWithCString:verified];
NSLog(#"%#", verified);
if([verified isEqualToString:#"admin"]){
NSLog(#"test admin");
[self performSelector:#selector(changeViewAdmin) withObject:nil afterDelay:0.05];
}
if([verified isEqualToString:#"user"]){
NSLog(#"test user");
[self performSelector:#selector(changeView) withObject:nil afterDelay:0.05];
}
if([verified isEqualToString:#"No"]){
NSLog(#"test no");
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"Invalid UserName/Password combination!"
delegate:self
cancelButtonTitle:#"Okay"
otherButtonTitles:nil];
[alert show];
[alert release];
}
[payloadAsString release];
//NSLog(#"%#", verified);
// INSERT GOOGLE MAPS URL REQUEST HERE
/*if(requestType == 1){
NSString* addressText = payloadAsString;
// URL encode the spaces
addressText = [addressText stringByAddingPercentEscapesUsingEncoding: NSASCIIStringEncoding];
NSString* urlText = [NSString stringWithFormat:#"http://maps.google.com/maps?q=%#", addressText];
// lets throw this text on the log so we can view the url in the event we have an issue
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:urlText]];
// */
//
//}
[connection release];
self.receivedData = nil;
}
Unfortunately, my console returns null and asks if I've put the -finishencoding method in. Question is, if that's correct, where would I do so?
PS: Another question, is if I'm retrieving an array of data from a database, is a PHP script the best way to go? Thank you.
1) Of all this code the only string relevant to your question is
NSArray *payloadAsString = [NSKeyedUnarchiver unarchiveObjectWithData:receivedData];
I really doubt that PHP script returns you data in NSKeyedUnarchiver-compatible format. I believe the only reason you don't get NSInvalidArgumentException exception from this method is that receivedData is nil (did you initialize it anywhere?). Try to make a string from what you receive like this
[[[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding] autorelease]
and log it. From this I hope it will be clear how to parse response.
2) Do not name NSArray instances like 'blahBlahString'. Strings and arrays are completely different.
NSKeyedUnarchiver can only unarchive instances which are produced by instances of the NSKeyedArchiver class.
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSKeyedUnarchiver_Class/index.html

how can I save data of UITextField into NSMutableArray?

I am writing a simple program having two UITextFields and one UIButton, when I press UIButton, a method is called, the coding of that method is as below
-(void) saveData{
restaurant_name_save = restaurant_name_textfield.text;
amount_name_save = amount_textfield.text;
order = [[NSMutableArray alloc] initWithObject: restaurant_name_save, amount_name_save, nil];
NSLog(#"%#", order);
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"warning" message:[NSString stringWithFormat:#"%d",[order count]] delegate:nil cancelButtonTitle:#"ok" otherButtonTitles:nil];
[alert show];
[alert release];
}
In NSLog, both UITextField data is showing properly, but in UIAlertView, it is ever showing 2, even when I change the data and again press button...
What should I do, I simply want to save the data for each time in NSMutableArray, as I press button,,
please help ....
I might be missing something here but
it says [order count]:
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"warning"
message:[NSString stringWithFormat:#"%d",[order count]]
delegate:nil cancelButtonTitle:#"ok" otherButtonTitles:nil];
The output of [order count] will be 2 because there are two entries in the array.
I would assume to see the contents of the array you write
stringWithFormat:#"%#", order
You might have a typo. Try using -initWithObjects: instead:
order = [[NSMutableArray alloc] initWithObjects: restaurant_name_save, amount_name_save, nil];
On repeat runs, also make sure you release the member variable order and set it to nil, before doing another +alloc and -initWithObjects: call:
if (order) {
[order release], order = nil;
}
order = [[NSMutableArray alloc] initWithObjects: restaurant_name_save, amount_name_save, nil];
...
Better yet, don't repeatedly use +alloc and -initWithObjects: in this method, but outside this method (perhaps in the larger object's init method) create an NSMutableArray of capacity 2:
self.order = [[[NSMutableArray alloc] initWithCapacity:2] autorelease];
In the method that handles the button action, set the items at their respective indices:
[order replaceObjectAtIndex:0 withObject:restaurant_name_textfield.text];
[order replaceObjectAtIndex:1 withObject:amount_textfield.text];
use initWithObjects insted of initWithObject
and always two objects set in the array and you are printing the count of array thats why it always show 2.
all other code written by you is correct.
If I follow you correctly, you want to have one array in which you want to store restaurant_name_save and amount_name_save each time a button on your screen is pressed. (I'm assuming this button calls the saveData method?
In that case, reallocating your array will clear any objects in it first and then adds the two strings to it. You should declare your array as a class variable and just do
[order addObject:string1];
[order addObject:string2];
EDIT:
Are you allocating it though? Here's a simple way of doing it, you might need to modify it to suit your needs -
Header file:
#interface WelcomeScreen : UIViewController {
NSMutableArray *array;
}
#property (nonatomic, retain) NSMutableArray *array;
-(IBAction) saveData:(id) sender;
Source file
#synthesize array;
-(void) viewDidLoad {
array = [[NSMutableArray alloc] init];
}
// Make the UIButton's tap option point to this -
-(IBAction) saveData:(id) sender
{
[array addObject:string1];
[array addObject:string2];
// alert.
}

NSMutableArray Memory Leak Issue

There are multiple memory leaks in this section of my code. Specifically with these arrays: PlaylistItem, PlaylistItemID and PlaylistItemLength. The problem is that I can't successfully release the arrays. When I attempt to use insert [xxxx release]; anywhere in this code, the app locks up. It's driving me absolutely nurtz!
-(void)configureCueSet {
MPMediaQuery *myPlaylistsQuery = [MPMediaQuery playlistsQuery];
NSArray *playlists = [myPlaylistsQuery collections];
//Get # of items in a playlist and names -------------------------------------
NSArray *songs;
for (MPMediaPlaylist *playlist in playlists) {
NSString *playListItem = [playlist valueForProperty: MPMediaPlaylistPropertyName];
if ([playListItem isEqualToString: savedLastSelectedPlaylist]){
songs = [playlist items];
}
}
PlaylistItem = [[NSMutableArray alloc] init];
PlaylistItemID = [[NSMutableArray alloc] init];
PlaylistItemLength = [[NSMutableArray alloc] init];
for (MPMediaItem *song in songs) {
[PlaylistItem addObject:[song valueForProperty: MPMediaItemPropertyTitle]];
[PlaylistItemID addObject:[song valueForProperty: MPMediaItemPropertyPersistentID]];
[PlaylistItemLength addObject:[song valueForProperty: MPMediaItemPropertyPlaybackDuration]];
}
}
Does that method get called multiple times? If so, your leak likely occurs on that assignment. You'd want:
[PlayListItem release];
PlaylistItem = [[NSMutableArray alloc] init];
[PlayListItemID release];
PlaylistItemID = [[NSMutableArray alloc] init];
[PlaylistItemLength release];
PlaylistItemLength = [[NSMutableArray alloc] init];
If you don't release what was there before, then you'll get a leak.
Attempting to insert [xxx release] would release the contents, not the arrays. The application crashes because with that you are deallocating the object which you are about to add to the array. According to the documentation (here), the values in an NSArray are automatically retained, and will be released as soon as the array is dealloc'ed. So, if you want to release any of those arrays, simply type [PlaylistItem release].

Being a good iPhone memory citizen

I am trying to clean up my code from memory leaks and I am having problems with the 'release' method.
Here is my code:
NSArray *dict = [[NSArray alloc] initWithContentsOfURL:url];
if (dict == nil) {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Error"
message:#"Cannot retrieve content. Please try again later."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
return;
}
self.schedule = dict;
[dict release];
[url release]; //I receive a runtime error here, "BAD ACCESS"
I don't understand why when I don't get the same problem with the line above [dict release];
Since you didn't post the code showing how URL was created, here's a general rule to follow:
If create the object with a initializer that starts with "init", then you should probably release it. If it's created another way (convenience method), then it's autoreleased. For example:
NSArray *a = [[NSArray alloc]initWithContentsOfURL:url]; // release this later
NSArray *a = [NSArray arrayWithContentsOfURL:url]; // this will be auto released
Basically you just need to look at whether the framework gave you an autoreleased object or not, because you can't release an autoreleased object or you'll (obviously) get a crash.
Take a look at the Memory Management Guide. It should be required reading.
You are responsible for calling release eat time, you call either alloc, copy, or retain.
In this case you called alloc on dict, but (presumably, although it is not shown where url comes from) not on url.
Objective-C allows you to send messages (e.g. 'release') to nil pointers without consequence.
If the pointer is non-nil and points to something bogus (i.e. an object that's been released), you'll get an EXC_BAD_ACCESS exception. Where does the url parameter come from and what is its retain count ([url retainCount]) before you call release?