Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-17249] iOS: ImageCache keeps uncompressed image data in memory

GitHub Issuen/a
TypeBug
PriorityHigh
StatusClosed
ResolutionInvalid
Resolution Date2014-07-01T21:53:52.000+0000
Affected Version/sRelease 3.2.3, Release 3.3.0
Fix Version/sn/a
Componentsn/a
Labelsn/a
ReporterChris Bowley
AssigneeIngo Muschenetz
Created2014-06-30T15:36:38.000+0000
Updated2014-07-01T21:53:52.000+0000

Description

When loading a remote image on iOS, the uncompressed image data is stored in memory for the duration of the app causing memory issues. Specifically: an instance of ImageCacheEntry class is instantiated for every image fetched by URL. This holds a reference to a UIImage created from the image data on disk: https://github.com/appcelerator/titanium_mobile/blob/master/iphone/Classes/ImageLoader.m#L76 As ImageCacheEntry objects are never released (AFAIK) image data is stored in memory for the duration of the app. One possible solution is to only keep cached image data on disk and return [UIImage imageWithContentsOfFile:localPath] for -[ImageCacheEntry fullImage] instead of holding a reference to the UIImage object:

- (UIImage *)fullImage {
	return [UIImage imageWithContentsOfFile:localPath];
}

This code snippet loads an image every second, running object allocations in Instruments shows the image data increasing (see attached screenshot).

function loadImage() {
	var url = "http://dummyimage.com/600x400/" + getRandomColor() + "/" + getRandomColor() + ".jpg&text=Hello";
	Ti.API.info(url);
	$.imageView.image = url;
}

function getRandomColor() {
    var letters = '0123456789abcdef'.split('');
    var color = '';
    for (var i = 0; i < 6; i++ ) {
        color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
}

$.index.open();

loadImage();

setInterval(function() {
	loadImage();
}, 1000);

Attachments

FileDateSize
image_cache_jira.png2014-06-30T15:36:38.000+0000712789
Screenshot 2014-07-01 19.00.21.png2014-07-01T18:16:08.000+0000220512

Comments

  1. Mauro Parra-Miranda 2014-07-01

    Hello, after doing a change in your code, i checked this running code at Instruments, without any sign of leak. Modified function:
       function loadImage() {
           var url = "http://dummyimage.com/600x400/" + getRandomColor() + "/" + getRandomColor() + ".jpg&text=Hello";
           Ti.API.info(url);
           imageView.image = null;
           imageView.image = url;
       }
       
    Full test case:
       var win = Ti.UI.createWindow({
       	backgroundColor:'white',
       });
       
       var imageView = Ti.UI.createImageView({
       	image:null,
       });
       
       function loadImage() {
           var url = "http://dummyimage.com/600x400/" + getRandomColor() + "/" + getRandomColor() + ".jpg&text=Hello";
           Ti.API.info(url);
           imageView.image = null;
           imageView.image = url;
       }
        
       function getRandomColor() {
           var letters = '0123456789abcdef'.split('');
           var color = '';
           for (var i = 0; i < 6; i++ ) {
               color += letters[Math.floor(Math.random() * 16)];
           }
           return color;
       }
        
       win.open();
       win.add(imageView);
       loadImage();
        
       setInterval(function() {
           loadImage();
       }, 1000);
       
    Best Regards
  2. Chris Bowley 2014-07-01

    Mauro, sorry if I didn't explain the issue clearly but I specifically didn't report this as a memory leak. Image data is cached and stored in memory for the lifetime of the application, whether or not the visible image view is destroyed. For each image fetched by URL we create an ImageCacheEntry object with a reference to a UIImage object containing the image data. This is held in memory. With or without nulling out the image view in JS, this reference is stored in memory for the life of the application. This does not result in a leak displayed in instruments because this is how the image cache has been implemented. However there is no way from JS to remove this cached data so it increases with every remote image fetched. My suggestion is to keep the cached image data on disk and only load into memory when required for display - see Description. Use the allocations instrument and you will see the number of ImageCacheEntry objects increasing and memory allocated to ImageIO_jpeg_Data increasing, as shown in my attached screenshot. I have run the code again with the additional line nulling out the image but the result is the same - see attached. Chris
  3. Mauro Parra-Miranda 2014-07-01

    Hello Chris! I tested again with your view (only tracking the allocations) and it's clear that the GC is kicking in and cleaning the ImageIO component (you will see a stair in the graph). Check the image here: http://picpaste.com/81ly5UL2.png From your screenshot, that is happening in iOS8. iOS 8 is not final yet, and likely to contain bugs (like a faulty GC). Best Regards
  4. Chris Bowley 2014-07-01

    Hi Mauro I'm not sure why but I don't see any GC behaviour on 3.2.3/3.3.0 iOS 7 on simulator and iPhone 5. ImageCacheEntry objects and ImageIO continue to increase. I'm using your code but would you be able to post your project so I can be sure I'm running the same thing? You can get my test project here: https://www.dropbox.com/s/0c3jabu6cd2fz3g/ImageCacheTest.zip.
  5. Mauro Parra-Miranda 2014-07-01

    Hello [~cbowley], here is my project: https://drive.google.com/file/d/0B439umN-AOyxRHpyTnBybW9fM0U/edit?usp=sharing Best Regards
  6. Chris Bowley 2014-07-01

    Mauro It looks like I simply haven't run the app for long enough for the cache to be purged - it took over 6 minutes to kick in. Sorry for raising the issue and thanks for investigating it. Chris

JSON Source