Render chart in mem / leaks


#1

Hi all, 

i am trying to create a number of in-memory charts which are to be saved to file (jpeg).  ultimatey these jpeg images are to be rendered into a pdf.  In our app we have a view controller with an embedded ShinobiChart control.   For off screen rendering I alloc & init the view controller in a loop and use services of the view controller to initialize the chart.  overall the process woirks, but i am experiencing leaks & ultimately a crash if i process too many charts.

below are code from the view controller &  view getImage.    I am seeing leaks assocaited with the chart drawViewHierarchyInRect.   Within the loop memory

grows by 5MB with each call  within the loop.   i have been playing around with autoreleasepool with no success.    Any ideas?   Is there a better way to handle in memory rendering of charts?

As well, i also seem to be experincing leaks with the call to UIImageJPEGRepresentation().   I realize this is probably not related to the chart directly.  Any suggestions on this one would be appreciated.

cheers,

mike

code snippet from ViewController.m

  Batch *b;
  NSData *imageData;
  UIImage *batchChart;
  BatchChartView *v;
  CGRect frame = CGRectMake(0,0,600,600);
  
  
  int n = 0;
  
  for( b in _batches)
  {
    
    @autoreleasepool
    {
      v = [[BatchChartView  alloc] initWithFrame:frame];
      
      [v setBatchData:b];
      
      
      // save image to temp directory.  chart.#.jpg 
      batchChart = [v getImage];
      
      // write image to disk  (full sized imag)
      NSString *fileName = [NSString stringWithFormat:@"chart.%d.jpg",n++];
      NSString *imageFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
      imageData = UIImageJPEGRepresentation(batchChart, 1.0);
      NSLog(@"%s %d: length %d", __func__ , __LINE__ ,[imageData length]);
      
      
      [imageData writeToFile: imageFilePath atomically:YES];
    }
    
    NSLog(@"%s %d", __func__ , __LINE__ );
  }

End ViewConroller.m

 

BatchChartView getImage

-(UIImage *)getImage
{
  UIImage *chartImage;
  @autoreleasepool
  {
    // Create an image context - the size of chart and the scale of the device screen
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(600,600), YES, 0.0);
    // Render our snapshot into the image context 
    [_chart drawViewHierarchyInRect:_chart.bounds afterScreenUpdates:YES];
    NSLog(@"%@",CGRectCreateDictionaryRepresentation(_chart.bounds));
    
    // Grab the image from the context
    chartImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
  }
  return chartImage;
}

end BatchChartView getImag

#2

Not sure how much different this is from yours, but in case it helps…

Here’s the code I use for iOS offscreen snapshotting, and it also shows how to annotate the image. My example uses C# and is creating an image to post to social media; you’ll have to translate to your environment. I have a couple of important routines:

  1. theGraph = MyGraphManager, which instantiates the chart and sets properties. It includes the key line:

    chart = new ShinobiChart (theView); // theView is the imageRect

  2. theGraph.setUpChart(), which tell theGraph to create axes and set the DataSource of the chart. This is a separate step because in my app you can change certain options (like feet vs meters) and I just call this routine to regenerate the series without creating an entirely new Chart object.

Make sure you clean up your graph when you’re done. Walk through it and clear arrays, dispose objects, and null your pointers to things you’ve created.

You may be posing more of an ObjectiveC question than a ShinobiChart one; I can’t judge whether your ALLOCS are really taken care of later in your construct. I’d be tempted to dispose of the resources at the end of each loop, rather than risk stranding them. I started writing this app in ObjC, but fled to C# when creating an Android version became so important. Don’t miss Objective C, it desparately needs replacing. More modern languages like C# or Swift are so much more productive.

Hope this helps.

Snapshotting code:

    public UIImage getFlightImage(){

            var imageSize = new SizeF (612, 612);
            var imageRect = new RectangleF (0, 0, 612, 612);
            Console.WriteLine ("got to MakeChartPicture");
            // Tne next three lines create the graph
            theGraph = new MyGraphManager (imageRect, MyDataArray);
            theChart = theGraph.chart;
            theGraph.setUpChart ();
            theChart.BackgroundColor = UIColor.Black;
            theChart.RedrawChart ();
            UIGraphics.BeginImageContextWithOptions(imageSize, false, 0f);
            theChart.DrawViewHierarchy(theChart.Bounds,true);
            // Create a view and add chart and annotations to it
            var theFlightGraphView = new UIView ();
            UILabel theSource = new UILabel ();
            theSource.Text = "Source: Jolly Logic";
            theSource.TextColor = UIColor.FromWhiteAlpha (1f, 0.4f);
            theSource.Font = UIFont.FromName ("Avenir", 12);
            theSource.SizeToFit ();
            theFlightGraphView.AddSubview (theSource);
            theFlightGraphView.Frame = new RectangleF (theChart.Bounds.Width - theSource.Bounds.Width - 3f, theChart.Bounds.Height - 0f - theSource.Bounds.Height, theSource.Bounds.Width, theSource.Bounds.Height);
            theSource.DrawViewHierarchy (theFlightGraphView.Frame, true);
            UIImage theFlightGraph = UIGraphics.GetImageFromCurrentImageContext (); 
            //NSData graphImageData = theFlightGraph.AsPNG (); // Use this to get a PNG
            UIGraphics.EndImageContext ();
            theGraph.doMyDispose;
            return theFlightGraph;
        }

#3

jbeans,  thanks for the code snippet.   I am posting my solution in the hopes it helps someone else.

i compared your solution with mine ( and code i posted).    I found the implementations to be similar.   I took your advice and focused on the objective c portion.

To clarify my earlier post I was not experiencing leaks per se, but found that resources were not being freed as promptly as I would have liked leading to app termination.   Using Instruments I tracked these down to the following allocatioons:

Category: VM CGImage 
Responsible Caller: CGBitmapAllocateData
size : 5.5MB

Category: SChartCrosshair(CALayer)
ResposibleCaller: vm_allocate
size 4.88 MB

these allocations were occurring once per loop (see for loop in my initial post).  They were not released within the loop.  Memory continued to grow till termination.   I ultimately updated my code as follows:

Batch *b;
  int n = 0;
  CGRect frame = CGRectMake(0,0,600,600);

  // allocate & init view outside loop.  
  // re-use it inside loop
  BatchChartView *v = [[BatchChartView alloc] initWithFrame:frame];

  
  for( b in _batches)
  {
   
    {
      
      [v setBatchData:b];
      
      
      // write image to disk (full sized imag)
      NSString *fileName = [NSString stringWithFormat:@"chart.%d.jpg",n++];
      NSString *imageFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
      
// using autoreleasepool here permits CGBitmapAllocateData (from within getImage) to be 
// released promptly
      @autoreleasepool
      {
        // save image to temp directory. chart.#.jpg 
        UIImage *batchChart = [v getImage];
        
        NSData *imageData = UIImageJPEGRepresentation(batchChart, 1.0);
        NSLog(@"%s %d: length %lu", __func__ , __LINE__ ,(unsigned long)[imageData length]);
        
        
        [imageData writeToFile: imageFilePath atomically:YES];
        imageData = nil;
        
        batchChart = nil;
      }
      
    }
  }

  [v cleanup];
  v = nil;
  1. within the loop, the @autoreleasepool addressed the CGBitmapAllocateData (via getImage)  allowing this to be released promptly.

  2. Rather than alloc & init view & embedded chart with each item in the loop, I moved the initialization outside the loop & reuse the view.  This eliminated the multiple SChartCrosshair allocations.

jbeans, thanks for your help.   Your insight got me looking in the right place(s).