Search code examples
iphonecocoa-touchmemory-managementuinavigationcontrollerrelease-management

Memory issue / overrelease with navigationcontroller and view containing sub CALayer


I have failed in solving this problem during the week-end... I thought to have a good knowledge in Objective-C memory management but this one is very hard to solve.

To sum-up context : I have a suclassed UIViewController that I push in my navigationcontroller. This UIViewController contains a UIScrollView (created in IB). In the UIViewController viewDidLoad() function, I add a background PDF (in a CATiledLayer sublayer of an UIview layer).

The problem is that when I pop the UIViewController from the navigation controller, the app crashes.

Using Zombies instruments and NSZombieLevel shows that it is because the UIViewController is over-released.

Here is the UIViewController :

@interface PlanViewController : UIViewController <UIScrollViewDelegate> {
    UIScrollView *panelScrollView;
    UIView *myContentView;
    CGPDFDocumentRef myDocumentRef;
    CGPDFPageRef myPageRef;
}

@property (nonatomic, retain) IBOutlet UIScrollView *panelScrollView;
@property (nonatomic, retain) UIView *myContentView;

@end

@implementation PlanViewController
@synthesize panelScrollView;
@synthesize myContentView;

- (void)viewDidLoad {
    [self setTitle:@"Plan"];

    myDocumentRef = CGPDFDocumentCreateWithURL((CFURLRef)[[NSBundle mainBundle] URLForResource:@"salonMap" withExtension:@"pdf"]);
    myPageRef = CGPDFDocumentGetPage(myDocumentRef, 1);
    CGRect pageRect = CGRectIntegral(CGPDFPageGetBoxRect(myPageRef, kCGPDFCropBox));

    CATiledLayer *tiledLayer = [[CATiledLayer alloc] init];
    [tiledLayer setDelegate:self];
    [tiledLayer setTileSize:CGSizeMake(1024.0, 1024.0)];
    [tiledLayer setLevelsOfDetail:4];
    [tiledLayer setLevelsOfDetailBias:8];
    [tiledLayer setFrame:pageRect];

    UIView *newView = [[UIView alloc] initWithFrame:pageRect];
    [newView.layer addSublayer:tiledLayer];
    [newView setTag:555];
    [self setMyContentView:newView];
    [newView release];
    [tiledLayer release];

    [panelScrollView setDelegate:self];
    [panelScrollView setContentSize:pageRect.size];
    [panelScrollView setMaximumZoomScale:5];
    [panelScrollView addSubview:myContentView];
    [panelScrollView setClipsToBounds: YES];
    [panelScrollView setMaximumZoomScale:20.0];
    [panelScrollView setMinimumZoomScale:1.0];
    [panelScrollView setZoomScale:1];

    [self initPinsOnMap];
}

- (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx
{
    CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 1.0);
    CGContextFillRect(ctx, CGContextGetClipBoundingBox(ctx));
    CGContextTranslateCTM(ctx, 0.0, layer.bounds.size.height);
    CGContextScaleCTM(ctx, 1.0, -1.0);
    CGContextConcatCTM(ctx, CGPDFPageGetDrawingTransform(myPageRef, kCGPDFCropBox, layer.bounds, 0, true));
    CGContextDrawPDFPage(ctx, myPageRef);
}

- (void)dealloc {
    [panelScrollView release];
    [myContentView release];
    CGPDFDocumentRelease(myDocumentRef), myDocumentRef = NULL;
    myPageRef = NULL;
    [super dealloc];
}

@end

After having played with // during hours, I discovered that the over released issue only occurs if I "[newView.layer addSublayer:tiledLayer];". If there is no subLayer added, the UIViewController is not overreleased.

I push my ViewController like this :

PlanViewController *trailsController = [[PlanViewController alloc] initWithNibName:@"PlanView" bundle:nil ];
[self.navigationController pushViewController:trailsController animated:YES];
[trailsController release];

I am wondering what I am doing wrong. I am sure to respect Memory Management guide. I think the issue comes from CALayer. I tried with and without using a pdf in the CALayer but it changes nothing...

But no matter how I code it, it always lead to a : ** -[PlanViewController isKindOfClass:]: message sent to deallocated instance 0xc641750**

Instruments Zombies show me an overrelease on :

88 PlanViewController Zombie -1 6909541120 0x64a6a00 0 UIKit -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:]

Do you have an hint ?

Thanks a lot for your help


Solution

  • SOLVED !!! Maybe it could help :

    In fact, asynchronous redraws are called by CATiledLayer. When releasing UIViewController you have to say to CATiledLayer that its delegate is not anymore alive. I added a reference (assign) to my CATiledLayer and in my dealloc function i do :

    self.PDFTiledLayer.contents=nil;
    self.PDFTiledLayer.delegate=nil;
    [self.PDFTiledLayer removeFromSuperlayer];
    

    I hope you won't lose as many time I lost...