views:

1078

answers:

4

Hi guys, I'm experiencing a hard issue here, would appreciate any, I mean ANY help =)

I'm a experienced developer by I'm new to Objective-C/iPhone/Cocoa.

I wanna create a class controller which I'm able to pass a NSMutableArray as a parameter.

Then, we have:

selTimeIntController = [[SingleSelectPickerViewController alloc] initWithSettings: listOfIntervals :kAlarmIntervalStr :myDataHolder.alarmInterval];
[self.navigationController pushViewController: selTimeIntController animated: YES];
[selTimeIntController release];

where this listOfIntervals is an already alloc/init NSMutableArray*.

on my SingleSelectPickerViewController, we have:

-(id)initWithSettings:(NSMutableArray*)sourceArray :(NSString*)viewCurrentValue :(NSString*)viewTitle {

    if(self = [self initWithNibName: kNibName bundle: [NSBundle mainBundle]]) {

            listOfIntervals = [NSMutableArray arrayWithArray: (NSMutableArray*)sourceArray];
            currentValue    = [[NSString alloc] initWithString: viewCurrentValue];
            title           = [[NSString alloc] initWithString: viewTitle];
    }

    return self;
}

Through debugging I'm able to see my listOfIntervals being created on my SingleSelectPickerViewController.

Here we have the SingleSelectPickerViewController' dealloc:

- (void)dealloc {
    [super dealloc];

    [listOfIntervals release];
    [currentValue    release];
    [title           release];
}

But, everytime I instantiate my SingleSelectViewController, I receive right afterwards an EXEC_BAD_ADDRESS with the following stack:

#0  0x96132688 in objc_msgSend ()
#1  0x00003ee2 in -[SingleSelectPickerViewController tableView:numberOfRowsInSection:] (self=0xd38940, _cmd=0x319a6bc0, tableView=0x102e000, section=0) at /Users/Cadu/iPhone/myApp/Classes/SingleSelectPickerViewController.m:115
#2  0x30a86bb4 in -[UISectionRowData refreshWithSection:tableView:tableViewRowData:] ()
#3  0x30a8879b in -[UITableViewRowData rectForFooterInSection:] ()
#4  0x30a883c7 in -[UITableViewRowData heightForTable] ()
#5  0x3094e8e6 in -[UITableView(_UITableViewPrivate) _updateContentSize] ()
#6  0x30940a7d in -[UITableView noteNumberOfRowsChanged] ()
#7  0x3094a2a0 in -[UITableView reloadData] ()
#8  0x30947661 in -[UITableView layoutSubviews] ()
#9  0x00b41d94 in -[CALayer layoutSublayers] ()
#10 0x00b41b55 in CALayerLayoutIfNeeded ()
#11 0x00b413ae in CA::Context::commit_transaction ()
#12 0x00b41022 in CA::Transaction::commit ()
#13 0x00b492e0 in CA::Transaction::observer_callback ()
#14 0x30245c32 in __CFRunLoopDoObservers ()
#15 0x3024503f in CFRunLoopRunSpecific ()
#16 0x30244628 in CFRunLoopRunInMode ()
#17 0x32044c31 in GSEventRunModal ()
#18 0x32044cf6 in GSEventRun ()
#19 0x309021ee in UIApplicationMain ()
#20 0x000020d8 in main (argc=1, argv=0xbffff0b8) at /Users/Cadu/iPhone/MyApp/

Any idea of what's going on?

A: 

I'm new to Mac programming, but I think your dealloc method is in the wrong order.

It should be:

- (void)dealloc {
    [listOfIntervals release];
    [currentValue    release];
    [title           release];

    [super dealloc];
}

You should fix that, altough I don't think that this will solve your problem.

Also I don't get what you are doing here:

if(self = [self initWithNibName: kNibName bundle: [NSBundle mainBundle]]) {
    //...
}

I think that should be:

if ( ! [super initWithNibName: kNibName bundle: [NSBundle mainBundle]] ) {

       return nil;
}

//...
André Hoffmann
Nonsense, [super dealloc] must always be the last item in the -dealloc or else you will very likely crash.
bbum
It does matter. Once [super dealloc] runs, the space that stores your instance variables is freed, so doing *anything* within the class is invalid.
Chuck
Not that this particular answer will fix the problem, though.
bbum
Why -1, I already stated that this will not fix the problem. It was just a hint as it's cleary wrong as you said yourself.
André Hoffmann
I had previous stated that the order of items in the dealloc method didn't matter. Apologies for completely misunderstanding that one: I thought André meant that the relative ordering of the calls BEFORE [super dealloc] mattered. Really what he meant, that I misunderstood, is that [super dealloc] must always be called last (which is true)
Mike Abdullah
A: 
  1. What about sourceArray - is it retained somewhere? You have to retain allocated object in order to use it beyond its scope, otherwise it's autoreleased. You can use the simple reference to it in your class without creating the array again.

Or, you can

listOfIntervals = [[NSMutableArray arrayWithArray: (NSMutableArray*)sourceArray] retain];

and then use it and release it.

  1. The answer above has noticed, that you call the [super dealloc] before releasing all your class allocations. [super dealloc] should be called in the end.

  2. There are plenty of useful links about Cocoa memory management, especially about usage of alloc/retain functions. This is really essential part of Cocoa/iPhone programming. See this one for example: Memory management in Cocoa or just google for it

Hope it helps, good luck

Nava Carmon
A: 

You need to go back to basics on this one.


Problem 1: You are passing around a mutable foundation object.

This is almost always indicative of bad design. Look at Cocoa/CocoaTouch and you'll see very few uses of mutable classes being passed as parameters or returned. Doing so is almost always because of performance constraints.

Why is it bad? Because after making the call, it's quite easy to end up with 2 or more objects all sharing the same mutable object. If one makes a change to it, the others have no way of knowing about it, potentially causing some very strange behaviours further down the line. NOT fun to debug.


Problem 2: You are not retaining the array

This is a memory management fundamental and you absolutely MUST understand it before trying to dig yourself in too much. Apple can probably explain it better than I, but it boils down to:

If you need access to an object outside of the scope where you were given it, retain it. Release it when you are done later.

So what you're doing here is assigning an autoreleased array into the listOfIntervals instance variable, and of course it gets released & deallocated later, blowing up your app when you next try to access it. Instead, here's the correct code:

- (id)initWithIntervals:(NSArray *)sourceArray
           currentValue:(NSString *)viewCurrentValue
                  title:(NSString *)viewTitle
{
  if (self = [self initWithNibName:kNibName bundle:nil])
  {
    listOfIntervals = [sourceArray mutableCopy];
    currentValue    = [viewCurrentValue copy];
    title           = [viewTitle copy];
  }

  return self;
}

Points of note:

  • This method is properly named. It has clear names for each argument.
  • There's no need to call [NSBundle mainBundle]. As the documentation states, NSViewController will figure this out for itself.
  • All the arguments passed in are copied. This has two important effects:
    • NSArray and NSString are value objects. That is, your code is interested in their value, not the actual object itself. Cocoa will nicely take care of making this as efficient as possible.
    • -copy and -mutableCopt both return objects with a +1 retain count, so they won't be going anywhere until you release them. Perfect for a typical instance variable.
Mike Abdullah
Mike, thanks for pointing out the NSMutableArray x NSArray stuff. You're correct and, indeed, my design was wrong.
Cadu
+3  A: 

The title of the question says "memory leak". Everything in the question indicates "crasher". This is a crasher, not a memory leak. Or, at least, you won't know if you have a memory leak or not until you fix the crashers.

The most likely source of the crash is the incorrect management of the listOfIntervals instance variable.

listOfIntervals = [NSMutableArray arrayWithArray: (NSMutableArray*)sourceArray];

Specifically, this needs to be:

listOfIntervals = [[NSMutableArray arrayWithArray: sourceArray] retain];

As Mike indicated above, passing around a mutable collection reference is probably a bad idea. What happens if sourceArray changes out from under your class? Are you prepared to deal with that?

A more common idiom would be to declare your method as taking an NSArray* and then copy the array:

listOfIntervals = [sourceArray mutableCopy]; // or -copy, if you don't need it to be mutable

(1) The (NSMutableArray*) cast was unnecessary. Did know harm, but why have it if it isn't needed?

(2) You need to retain listOfIntervals. +arrayWithArray: will create an autoreleased array and, thus, the array was being released after the object was initialized, which would lead to the crash you see.

(3) -copy & -mutableCopy return retained objects, no need to call -retain.

However, you also need to fix your -dealloc method:

- (void)dealloc {
    // move this [super dealloc];

    [listOfIntervals release];
    [currentValue    release];
    [title           release];
    [super dealloc]; // to here
}

The [super dealloc] must always be last. There is nothing magic about -dealloc and, thus, by having that call first, you were telling the instance to deallocate itself and then going through and cleaning up the instance variables. That would have led to a second crash (or unexpected behavior).

Overall, I would suggest you re-read the memory management guidelines.

http://developer.apple.com/iPhone/library/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html

bbum
Great answer; not only is it right, but it deserves an upvote for the care and attention to detail alone.
Jarret Hardie
Good answer. I would like to add that as a rule of thumb you can match how to setup your instance variables in your init-method with how you would declare the same instance variable as a @property.retain do `ivar = [obj retain]`, copy do `ivar = [obj copy]`, assign do `ivar = obj`. Then in dealloc do `[ivar release]` on retain and copy @property backings only.
PeyloW
The best answer. Thanks man, I have 5 years+ on C/C++ stuff, and I've never saw some SO delicated memory treatment. This is good from a side, but also annoying. I re-read the memory management guidelines and now i'm using Instruments to assure everything is fine. Thanks for helping =)
Cadu
Cadu, I'm glad you've got the information you need. Don't forget to accept the answer if it is, indeed, "the best answer"... it makes the world go 'round :-)
Jarret Hardie