views:

880

answers:

4

I am writing a program which redraws a graph every so often. Unfortunately it seems that for some reason core graphics does not seem to release the previous graphic so I run into memory problems fast. How can I force it to release memory at the end of each draw?

Not that this is not a memory leak as the memory does get released when I leave close that view. I have tried moving the NSTimer to several places in the code to no avail.

Here is the code below with the files going from most important to (potentially) irrelevant.

First is the core file+header which draws the graphic (in this case a sine wave) every so often.

GraphicsView.h

#import <UIKit/UIKit.h>
#import "Model.h"

@interface GraphicsView : UIView 
{
    Model * model;
} 
@end

GraphicsView.m

#import "GraphicsView.h"

@implementation GraphicsView

- (id)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) 
    {
     model=[Model sharedModel];
     [NSTimer scheduledTimerWithTimeInterval:.010 target:self selector:@selector(setNeedsDisplay) userInfo:nil repeats:YES];
    }
    return self;
}



- (void)drawRect:(CGRect)rect 
{
    int now=[model n];

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextClearRect(context, CGRectMake(0, 0, rect.size.width, rect.size.height));
    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);

    CGContextMoveToPoint(context, 0.0, 0.0);
    double xc_old=0;
    double yc_old=100;
    for (int i=0;i<now;i++)
    {
     double xc=(double)i/(double)now*200;
     double yc=100-100*sin((double)i/6);
     CGContextMoveToPoint(context, xc_old, yc_old);
     CGContextAddLineToPoint(context, xc, yc);
     CGContextStrokePath(context);
     xc_old=xc;
     yc_old=yc;
    }
}


- (void)dealloc {
    [super dealloc];
}
@end

The view controller header:

#import <UIKit/UIKit.h>
#import "GraphicsView.h"


@interface SbarLeakAppDelegate : NSObject <UIApplicationDelegate>
{
    UIWindow *window;
    Model *model;
    GraphicsView * gv;
    NSTimer * timer;
}

@end

The view controller itself.

#import "SbarLeakAppDelegate.h"

@implementation SbarLeakAppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application 
{       
    model=[Model sharedModel];

    CGRect appRect;
    CGRect frame = CGRectInset(appRect, 0.0f, 0.0f);
    appRect.origin=CGPointMake(0.0, 40.0);
    appRect.size = CGSizeMake(200.0f, 200.0f);
    frame = CGRectInset(appRect, 0.0f, 0.0f);
    gv=[[GraphicsView alloc] initWithFrame:appRect];
    [gv setFrame:frame];
    [window addSubview:gv];
    [gv release];


    [window makeKeyAndVisible];
}

-(void) viewDidAppear:(id)sender
{
    //timer=[NSTimer scheduledTimerWithTimeInterval:.250 target:gv selector:@selector(setNeedsDisplay) userInfo:nil repeats:YES];
}


- (void)dealloc 
{
    [window release];
    [super dealloc];
}
@end

This code is only included for completeness but (should not) affect the question. The data source header file (just a simple integer)

#import <UIKit/UIKit.h>


@interface Model : NSObject 
{
    int n;
}

@property int n;
+(Model *) sharedModel;
-(void) inc;
@end

The data source, Model.m:

#import "Model.h"

@implementation Model
static Model * sharedModel = nil;

+ (Model *) sharedModel
{
    if (sharedModel == nil)
     sharedModel = [[self alloc] init];
    return sharedModel; 
}

@synthesize n;

-(id) init
{
    self=[super init];
    [NSTimer scheduledTimerWithTimeInterval:.05 target:self selector:@selector(inc) userInfo:nil repeats:YES];
    return self;
}

-(void) inc
{
    n++;
}
@end
A: 

According to the NSTimer documentation, the max time resolution is 50-100ms... it looks like you're trying for 1ms... according to spec a timer that asks for finer resolution than that shouldn't cause memory issues, it just shouldn't fire as often as needed. I suspect that's part of your problem, though.

NSTimer Class Reference

Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds.

Robert Karl
In the full program the timer runs once a second. That was a typo when I was creating the example. So, no the timer does not solve the memory issue/
John Smith
+1  A: 

The selector you use for your timer does not have the correct signature.

From the Apple docs:

The selector must have the following signature:

- (void)timerFireMethod:(NSTimer*)theTimer

You should use your own method for the timer and call setNeedsDisplay from there. As Robert said, you should turn down the frequency a bit, but I don't think either of these issues will solve your problem.

Nikolai Ruhe
That is not the point of the question.
John Smith
... but valuable information if you go for correctness.
Nikolai Ruhe
By saying that these issues won't solve your problem, I meant to say: The code you provided is correct and has no memory issues. Search elsewhere or provide more code.
Nikolai Ruhe
A: 

Hi,

did you find any solution for this?

George
downvote. not an answer.
Shivan Raptor
A: 

+ (Model *) sharedModel { if (sharedModel == nil) sharedModel = [[self alloc] init]; return sharedModel; } in this snippet, autorelease is missing. This also leads to a leak i think. try with sharedModel = [[[self alloc] init] autorelease];

selvam
It might lead to a leak, but since it is initialized only once at the beginning of the program it's not a problem.
John Smith