0

One of the views od my app shows a list of images. When I scroll that list several times then my app crashes. I profiled it with instrument and it seems that the cells of the list are taking more memory as the list is scrolled.

Should a custom UITableCell be 'autoreleased' when returned from tableView:cellForRowAtIndexPath: ? (if I do, I have a crash on iOS 4.3 / it's fine in iOS 5.0 and 6.1)

This custom UITableCell have several pictures drawn into its 'contentView'. Those pictures are actually custom UIButton in which I set the background image.

The images are managed with HJManagedImageV

code for custom UIButton :

@implementation ProductTileButtonIpad

@synthesize product;

- (id)initWithFrame:(CGRect)frame andProduct:(Product *)aProduct {

    if (self = [super initWithFrame:frame]) {

        self.product = aProduct;

        self.productTileView = [[[HJManagedImageV alloc] initWithFrame:self.frame] autorelease];
        self.productTileView.callbackOnSetImage = self;
        self.productTileView.url = some picture url

        [[ImageManager instance] manage:self.productTileView];
    }

    return self;
}


#pragma mark -
#pragma mark HJManagedImageV delegate

-(void) managedImageSet:(HJManagedImageV*)mi {

    [self setBackgroundImage:mi.image forState:UIControlStateNormal];
}

-(void) managedImageCancelled:(HJManagedImageV*)mi {
}


- (void)dealloc {

    NSLog(@"deallocating ProductTileButtonIpad");

    [self.product release];
    [self.productTileView release];

    [super dealloc];
}

@end

code for the custom cell

@implementation ProductGridCellIpad

@synthesize products, parentController;

- (void)initializeWithProducts:(NSMutableArray *)productsToShow{

    self.products = productsToShow;

    // clear possible old subviews
    for (UIView *v in self.contentView.subviews) {
        [v removeFromSuperview];
    }

    NSInteger width = 240;
    NSInteger height = 240;

    Product *product0 = [products objectAtIndex:0];

    self.productTile0 = [[[ProductTileButtonIpad alloc] initWithFrame:CGRectMake(12, 12, width, height) andProduct:product0] autorelease];

    [self.productTile0 addTarget:self.parentController action:@selector(selectedProduct:) forControlEvents:UIControlEventTouchUpInside];

    [self.contentView addSubview:self.productTile0];

    [self.productTile0 release];

    if ([self.products count] > 1) {

        Product *product1 = [products objectAtIndex:1];

        self.productTile1 = [[[ProductTileButtonIpad alloc] initWithFrame:CGRectMake(12 + width + 12, 12, width, height) andProduct:product1] autorelease];

        [self.productTile1 addTarget:self.parentController action:@selector(selectedProduct:) forControlEvents:UIControlEventTouchUpInside];

        [self.contentView addSubview:self.productTile1];

        [self.productTile1 release];
    }

    if ([self.products count] > 2) {

        Product *product2 = [products objectAtIndex:2];

        self.productTile2 = [[[ProductTileButtonIpad alloc] initWithFrame:CGRectMake(2*(12 + width) + 12, 12, width, height) andProduct:product2] autorelease];

        [self.productTile2 addTarget:self.parentController action:@selector(selectedProduct:) forControlEvents:UIControlEventTouchUpInside];

        [self.contentView addSubview:self.productTile2];

        [self.productTile2 release];
    }
}

- (void)dealloc {

    NSLog(@"deallocating ProductGridCellIpad");

    if(self.products)
        [self.products release];

    if(self.productTile0)
        [self.productTile0 release];

    if(self.productTile1)
        [self.productTile1 release];

    if(self.productTile2)
        [self.productTile2 release];

    [super dealloc];
}

@end

and here's the code that creates the cell :

    NSString *productGridCellIpadIdentifier = @"ProductGridCellIpadIdentifier";

    ProductGridCellIpad *cell = [tableView dequeueReusableCellWithIdentifier:productGridCellIpadIdentifier];

    if(cell == nil) {

        cell = [[ProductGridCellIpad alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:productGridCellIpadIdentifier];

        [cell setFrame:CGRectMake(0, 0, self.view.frame.size.width, 244)];
    }

    [cell setParentController:self];
    [cell initializeWithProducts:products];

    return cell;

As it is now the code crashes right away on iOS 4.3. It works on iOS 5 and 6 but the app still crashes after a certain time of use/scrolling the table.

I don't use ARC.

I added some NSLog in the dealloc methods to see what's happening and I can see lots of "deallocating ProductTileButtonIpad" but I never see "deallocating ProductGridCellIpad"

My app easily reaches memory usage of 400Mb.

What am I doing wrong here?

If some of you have any thoughts, ideas that could help my understanding it would be much appreciated :)

Alexis
  • 16,629
  • 17
  • 62
  • 107
  • Not sure if that's it, but if you don't use ARC, you need to autorelease the cell. –  Feb 04 '13 at 07:59
  • 1
    The problem is you are initializing things every time, where you should do the initializations only once.. its like this when you are using tableview if(cell==nil){ //Do you initialization } //Here update only the values.. no initialization here.. – iphonic Feb 04 '13 at 08:01
  • @iphonic what do you mean by "you are initializing things every time" ? because I do use the cell recycling... – Alexis Feb 04 '13 at 08:39

2 Answers2

2

Cells are reused, so you shouldn't see "dealloc ProductGridCellIpad" when scrolling. Instead verify whether recycling really is working or whether you keep creating new cells all the time:

ProductGridCellIpad *cell = [tableView dequeueReusableCellWithIdentifier:productGridCellIpadIdentifier];

if(cell == nil) {
    cell = [[ProductGridCellIpad alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:productGridCellIpadIdentifier];
    [cell setFrame:CGRectMake(0, 0, self.view.frame.size.width, 244)];
    NSLog("Cell created");
}
else {
    NSLog("Cell recycled");
}

If that's ok, I'd check releasing. For example you have "self.productTile2" both autorelease and release, which might confuse memory management.

Also I'd check carefully the "parentController", which might prevent things from being released. You need to set it to nil.

JOM
  • 8,139
  • 6
  • 78
  • 111
  • The recycling seems to work well : 4 cells are created (I guess that's because 4 cells are visible on the screen) and then they are all recycled but if I go back to the previous view then display the list again I can see 4 new cells created. So if I keep doing that the memory usage will be increased right? – Alexis Feb 04 '13 at 08:30
  • About the releasing what should I do then in the present case? release or autorelease ? I don't really get the difference between them except that I can't touch a ref after I called release on it... – Alexis Feb 04 '13 at 08:41
  • Use either autorelease (when system does release for you after object is not in use any more) or use release (when you tell system that object is ready for release at next time things are released) but DO NOT use both at the same time. – JOM Feb 04 '13 at 19:54
  • So going away and coming back never releases anything? Then it could be cell.parentController blocking objects from release. – JOM Feb 04 '13 at 19:57
  • indeed it seems that the reference to parentController prevent the cell from being released. How can it be? I know that an object cannot be deallocated if another object still holds a reference to it but here it's the other way around right? Could that work with a weak reference then? – Alexis Feb 05 '13 at 10:45
  • I eventually got rid of parentController. I found a workaround. – Alexis Feb 05 '13 at 11:50
  • @Alexis Hey what was the workaround that you found, could you explain it in swift if possible? Thank you – Ben Akin Jan 11 '19 at 06:24
1

Yes, you obviously have to autorelease your cell when using alloc] init.. dequeueReusableCellWithIdentifier returns autoreleased one.

sunnycows
  • 125
  • 5