user567677
user567677

Reputation:

My NSMutableArray loses its objects outside of function scope even after alloc / init

I'm totally stumped on this one. I have an NSMutableArray which is declared in my header and set as a property, synthesized etc. I then call a function that allocates and initializes the array, and I add custom objects to it. I do a for each loop after the objects are added to ensure that they are actually contained within the array and they are. Once the program goes outside of this function scope, though, suddenly the array is empty.

header file:

@interface ScheduleViewController : UITableViewController  {

    NSString *login_id;
    NSMutableArray *events;
}

- (id)initWithID:(NSString*)l_id;
- (void)grabURLInBackground; // ASIHTTP example method

@property (nonatomic, retain) NSString *login_id;
@property (nonatomic, retain) NSMutableArray *events;

@end

implementation:

@synthesize events;

- (void)requestFinished:(ASIHTTPRequest *)request
{   
    // Use when fetching text data
    NSString *response = [request responseString];
    SBJsonParser *parser = [[SBJsonParser alloc] init];
    NSArray *eventDics = [parser objectWithString:response error:nil]; // an array of dictionaries of events
    NSDateFormatter *dateForm = [[NSDateFormatter alloc] init];

    // Allocate empty event object and initialize the mutable array
    Event* event = [[Event alloc] init];
    self.events = [[NSMutableArray alloc] initWithCapacity:[eventDics count]];

    // loop through the array of dictionaries
    for (int i = 0; i < [eventDics count]; i++)
    {
        NSDictionary *dict = [eventDics objectAtIndex:i];
        for(NSString *key in dict) {    
            // for the sake of readability i wont include the code
                    // but the event is populated here
            }
        [self.events addObject:event];
        [event release];
    }
    NSLog(@"Array Count: %i", [self.events count]);
    for (Event *e in events) {
        NSLog(@"eventid: %i, type: %@, price: %f, name: %@", e.event_id, e.type, e.price, e.name);
}
    [parser release];
    [dateForm release];
}

So the above code works fine and prints out the variables from the Event objects that are stored in the events mutable array.

What I want to do is use the events array in another function now, and when I try to, the count is 0 and also no objects are stored in the array when I look at it.

In viewDidUnload I set self.events = nil; and in dealloc I do [self.events release]

Upvotes: 0

Views: 2613

Answers (3)

user567677
user567677

Reputation:

So I figured out the problem and it's due to an error on my part. After stepping through the function calls more closely, it turns out that the table view delegate method

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

was being called before populating the array with requestHasFinished. I was calling requestHasFinished indirectly through viewDidLoad but I think that the table view delegate method was being called when the view controller is initialized. Init was being called before viewDidLoad because the view controller is actually handled within a tab view controller which initializes all of the view controllers for each tab at the time when itself is initialized. That's another matter to get into.. I'm not sure if I like everything being initialized and setup before the views are even displayed but.. something to research.

Anyways thanks again for the help.

Upvotes: 1

gamozzii
gamozzii

Reputation: 3921

You are doing your alloc/init for the Event *event object outside of your for loop. This means you are adding the same object every time you add it to the array. You should move this line:

Event* event = [[Event alloc] init];

To the inside of your

for (int i=0 ...  loop.

Not sure that would explain the symptoms you are seeing, but it could, since the following statement:

[event release] 

is also releasing that one allocated object once for every time through the loop - so you are releasing the object multiple times. If you move the Event alloc to the inside of the loop then the release will be ok. (adding the object to the array will retain it so its ok to release it, but you need to allocate a new Event each time through the loop).

Basically your code should look like this: (note I've also added an autorelease to your array alloc).

// Allocate empty event object and initialize the mutable array
self.events = [[[NSMutableArray alloc] initWithCapacity:[eventDics count]] autorelease]; // assigning to the retain property will retain it, so autorelease it or it will be retained twice.  Could also have used the arrayWithCapacity convenience method here instead and then wouldn't need to autorelease.

// loop through the array of dictionaries
for (int i = 0; i < [eventDics count]; i++)
{
    Event* event = [[Event alloc] init];  // Allocate a new Event each time through the loop so you are adding a unique object to the array each time.
    NSDictionary *dict = [eventDics objectAtIndex:i];
    for(NSString *key in dict) {    
        // for the sake of readability i wont include the code
                // but the event is populated here
        }
    [self.events addObject:event];
    [event release];
}

Upvotes: 4

Alex Nichol
Alex Nichol

Reputation: 7510

I see many problems with this code. These include the fact that you are releasing objects at inappropriate times, and that you are getting confused about the scope of different objects. It seems to me that one of the biggest problems that you are having is re-allocating your events array every time the requestFinished: method is called. In your init method, you should do something like this:

- (id)init {
    if ((self = [super init])) {
        // Since it is a retain property, we should autorelease it when
        // assigning to it, thus preventing an extra retain.
        self.events = [[[NSMutableArray alloc] initWithCapacity:[eventDics count]] autorelease];
    }
}

With that being said, here is how I would rewrite your requestFinished: method, as well as your dealloc method:

- (void)requestFinished:(ASIHTTPRequest *)request {   
    NSString *response = [request responseString];
    SBJsonParser *parser = [[SBJsonParser alloc] init];
    NSArray *eventDics = [parser objectWithString:response error:nil]; // an array of dictionaries of events
    NSDateFormatter *dateForm = [[NSDateFormatter alloc] init];

    // Clear the already allocated events array
    [self.events removeAllObjects];

    for (int i = 0; i < [eventDics count]; i++) {
        // note how I assign event in here
        Event *event = [[Event alloc] init];
        NSDictionary *dict = [eventDics objectAtIndex:i];
        for (NSString *key in dict) {    
            // Do whatever it is you do here
        }
        [self.events addObject:event];
        [event release];
    }
    NSLog(@"Array Count: %i", [self.events count]);
    for (Event *e in events) {
        NSLog(@"eventid: %i, type: %@, price: %f, name: %@", e.event_id, e.type, e.price, e.name);
    }

    [parser release];
    [dateForm release];
}

Finally, you can simply set the events property to nil in the dealloc method:

- (void)dealloc {
    self.events = nil;
    [super dealloc];
}

The only reason that I can think of for the array being empty is that a) it's contents are being deallocated, or b) it itself is being deallocated and set to nil. The pieces of your code that I fixed could possibly cause both of these. Try the changes that I have made, and see if they make a difference.

Upvotes: 2

Related Questions