views:

638

answers:

3

Hi Everyone.

I'm a beginner at C, Obj-C and the iPhone, and I'm trying getting to grips with a lot of terminology used. I hope one of ye can help with a problem I have been struggling with for a few days now.

My code below is a method which call up a nib containing a search field and a table. The table is populated from a search of the array created for 'theList' below. Using 'Instruments', I am getting a Leak at the line: NSDictionary *theItem = [NSDictionary dictionaryWithObjectsAndKeys:clientName,@"Name",clientId,@"Id",nil]; , but I can't figure out why :(

I know it's probably a difficult question to answer, but if any one can be of any help!

- (void)editClient:(id)sender {

    if (pickList == nil) {
     pickList = [[PickFromListViewController alloc] initWithNibName:@"PickList" bundle:nil];
    }

    TimeLogAppDelegate *appDelegate = (TimeLogAppDelegate *)[[UIApplication sharedApplication] delegate];
    NSMutableArray *theList = [[NSMutableArray alloc] init];
    int i;
    for (i=0;i < [appDelegate.clients count];i++) {
     Client *thisClient = [appDelegate.clients objectAtIndex:i];
     NSString *clientName = [[NSString alloc] initWithString: thisClient.clientsName];
     NSNumber *clientId = [[NSNumber alloc] init];
     clientId = [NSNumber numberWithInt:thisClient.clientsId];
     NSDictionary *theItem = [NSDictionary dictionaryWithObjectsAndKeys:clientName,@"Name",clientId,@"Id",nil];
     [theList addObject:theItem];
     theItem = nil;
     [clientName release];
     [clientId release];
    }
    [pickList createSearchItems:theList :NSLocalizedString(@"Client",nil)];
    [theList release];

    appDelegate.returningID = [NSNumber numberWithInt: projectsClientsId];
    [self.navigationController pushViewController:pickList animated:YES];

}

Thanks in advance!

+3  A: 

You are creating clientID with [[NSNumber alloc] init], and then immediately overwriting it with an autoreleased NSNumber instance [NSNumber numberWithInt], and then you are releasing it later in your code, which you shouldn't do. Get rid of the [[NSNumber alloc] init] line and the [clientId release] line and that should fix it up a little.

dreamlax
Many thanks for your answer. I actually had it as you suggest, but changed that code thinking it may solve the problem. It is now back as you suggested, but with the same leak results. Can I take it that the problem lies within the called nib?
Chris
+8  A: 

This returns allocated NSNumber instance.

NSNumber *clientId = [[NSNumber alloc] init];

This line overwrites the above clientId with another instance of NSNumber, numberWithInt returns autoreleased object, since you haven't allocated memory for it you should not call release, it will be released automatically.

clientId = [NSNumber numberWithInt:thisClient.clientsId];

You are calling release on clientId so you get memory problem. To fix it remove the first line above which is useless in this case and update the second one to:

NSNumber * clientId = [NSNumber numberWithInt:thisClient.clientsId];

Then remove the:

[clientId release]

Because the clientId will be released automatically.

EDIT: Re still have problems ... I'm not sure how to you manipulate the clients in app delegate, otherwise the code should work ok, I created small example, omitting the parts that I can't see (app delegate and clients):

// command line utility - foundation tool project:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    NSMutableArray * theList = [[NSMutableArray alloc] init];

    int i = 0;
    for (i = 0; i < 10; ++i)
    {
        NSString * clientName = [NSString stringWithString:@"client"];  //no need to release
        NSNumber * clientId = [NSNumber numberWithInt:i];
        NSDictionary * theItem = [NSDictionary dictionaryWithObjectsAndKeys:
                                  clientName, @"name",
                                  clientId, @"id",
                                  nil];

        [theList addObject:theItem];
    }

    for (id item in theList) for (id key in item) NSLog(@"%@ - %@", key, [item objectForKey:key]);

    [theList release];
    [pool drain];
    return 0;
}
stefanB
Thanks for your time! I actually had it as you suggest, but changed that code thinking it may solve the problem. It is now back as you suggested, but with the same leak results. Can I take it that the problem lies within the called nib?
Chris
what's the error that you're getting?
stefanB
Chris
Hm it's difficult to say based on your description, there might be error in any of those steps. If clients exist during the whole operation maybe try just retaining references to mutable array instead of copying around data.
stefanB
I'll try it. Thanks again for your time and help. It's very generous of you.
Chris
+1  A: 

Aside from the obvious leak of the NSNumber, there are a few other things I'd fix that may help. Most are fairly minor, but in my experience with Objective-C, less code == clearer code, something that is not equally true for languages like Bash or Perl. ;-)

- (void) editClient:(id)sender {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  if (pickList == nil) {
    pickList = [[PickFromListViewController alloc] initWithNibName:@"PickList" bundle:nil];
  }
  TimeLogAppDelegate *appDelegate = (TimeLogAppDelegate*)[[UIApplication sharedApplication] delegate];

  NSMutableArray *searchItems = [NSMutableArray array];
  NSMutableDictionary *itemDict = [NSMutableDictionary dictionary];
  for (Client *client in appDelegate.clients) {
    [itemDict setObject:[client.clientsName copy]                 forKey:@"Name"];
    [itemDict setObject:[NSNumber numberWithInt:client.clientsId] forKey:@"Id"];
    [searchItems addObject:[[itemDict copy] autorelease]];
  }
  [pickList createSearchItems:searchItems :NSLocalizedString(@"Client",nil)];
  [self.navigationController pushViewController:pickList animated:YES];
  appDelegate.returningID = [NSNumber numberWithInt: projectsClientsId];
  [pool drain];
}

There are a few mysterious points that make me suspicious:

  • The line just after the for loop tells pickList to do something with the NSMutableArray. That method should retain the new array, as well as release the old array if one exists. If you just overwrite the pointer, the old array will be leaked. (Also, this method is poorly named. Anonymous arguments (a colon with no preceding text) are legal in Objective-C, but considered extremely bad practice. Consider renaming the method to better express what it does.)
  • The next line seems to associate the pick list with a navigation controller. If it is custom code, make sure the -pushViewController:animated: method properly releases an existing pick list when a new one is specified.
  • Assigning to appDelegate.returningID is assumed to call the setter for a returningID property. Be sure that property retains or copies the NSNumber as necessary.

Memory leaks can be tricky to track down, even in Instruments, and you'll often find that it looks like Foundation classes (such as NSDictionary) are leaking like a sieve, but I have always been able to trace it back to an abnormality in my code. :-)

Quinn Taylor