{ "id": "132409", "key": "TIMOB-17249", "fields": { "issuetype": { "id": "1", "description": "A problem which impairs or prevents the functions of the product.", "name": "Bug", "subtask": false }, "project": { "id": "10153", "key": "TIMOB", "name": "Titanium SDK/CLI", "projectCategory": { "id": "10100", "description": "Titanium and related SDKs used in application development", "name": "Client" } }, "fixVersions": [], "resolution": { "id": "7", "description": "", "name": "Invalid" }, "resolutiondate": "2014-07-01T21:53:52.000+0000", "created": "2014-06-30T15:36:38.000+0000", "priority": { "name": "High", "id": "2" }, "labels": [], "versions": [ { "id": "15971", "description": "Release 3.2.3", "name": "Release 3.2.3", "archived": false, "released": true, "releaseDate": "2014-04-30" }, { "id": "15422", "description": "Release 3.3.0", "name": "Release 3.3.0", "archived": false, "released": true, "releaseDate": "2014-07-16" } ], "issuelinks": [], "assignee": { "name": "ingo", "key": "ingo", "displayName": "Ingo Muschenetz", "active": true, "timeZone": "America/Los_Angeles" }, "updated": "2014-07-01T21:53:52.000+0000", "status": { "description": "The issue is considered finished, the resolution is correct. Issues which are closed can be reopened.", "name": "Closed", "id": "6", "statusCategory": { "id": 3, "key": "done", "colorName": "green", "name": "Done" } }, "components": [], "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.\r\n\r\nSpecifically: 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:\r\n\r\nhttps://github.com/appcelerator/titanium_mobile/blob/master/iphone/Classes/ImageLoader.m#L76\r\n\r\nAs ImageCacheEntry objects are never released (AFAIK) image data is stored in memory for the duration of the app.\r\n\r\nOne 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:\r\n\r\n{code:title=ImageLoader.m}\r\n\r\n- (UIImage *)fullImage {\r\n\treturn [UIImage imageWithContentsOfFile:localPath];\r\n}\r\n\r\n{code}\r\n\r\n\r\nThis code snippet loads an image every second, running object allocations in Instruments shows the image data increasing (see attached screenshot).\r\n\r\n{code}\r\n\r\nfunction loadImage() {\r\n\tvar url = \"http://dummyimage.com/600x400/\" + getRandomColor() + \"/\" + getRandomColor() + \".jpg&text=Hello\";\r\n\tTi.API.info(url);\r\n\t$.imageView.image = url;\r\n}\r\n\r\nfunction getRandomColor() {\r\n var letters = '0123456789abcdef'.split('');\r\n var color = '';\r\n for (var i = 0; i < 6; i++ ) {\r\n color += letters[Math.floor(Math.random() * 16)];\r\n }\r\n return color;\r\n}\r\n\r\n$.index.open();\r\n\r\nloadImage();\r\n\r\nsetInterval(function() {\r\n\tloadImage();\r\n}, 1000);\r\n\r\n{code}", "attachment": [ { "id": "49375", "filename": "image_cache_jira.png", "author": { "name": "cbowley", "key": "cbowley", "displayName": "Chris Bowley", "active": true, "timeZone": "Europe/London" }, "created": "2014-06-30T15:36:38.000+0000", "size": 712789, "mimeType": "image/png" }, { "id": "49410", "filename": "Screenshot 2014-07-01 19.00.21.png", "author": { "name": "cbowley", "key": "cbowley", "displayName": "Chris Bowley", "active": true, "timeZone": "Europe/London" }, "created": "2014-07-01T18:16:08.000+0000", "size": 220512, "mimeType": "image/png" } ], "flagged": false, "summary": "iOS: ImageCache keeps uncompressed image data in memory", "creator": { "name": "cbowley", "key": "cbowley", "displayName": "Chris Bowley", "active": true, "timeZone": "Europe/London" }, "subtasks": [], "reporter": { "name": "cbowley", "key": "cbowley", "displayName": "Chris Bowley", "active": true, "timeZone": "Europe/London" }, "environment": null, "closedSprints": [ { "id": 129, "state": "closed", "name": "To Be Scheduled", "startDate": "2016-03-28T04:03:55.142Z", "endDate": "2016-04-08T04:03:00.000Z", "completeDate": "2016-03-29T04:09:03.755Z", "originBoardId": 100 } ], "comment": { "comments": [ { "id": "311814", "author": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "body": "Hello,\r\n\r\nafter doing a change in your code, i checked this running code at Instruments, without any sign of leak. \r\n\r\nModified function:\r\n\r\n{code}\r\nfunction loadImage() {\r\n var url = \"http://dummyimage.com/600x400/\" + getRandomColor() + \"/\" + getRandomColor() + \".jpg&text=Hello\";\r\n Ti.API.info(url);\r\n imageView.image = null;\r\n imageView.image = url;\r\n}\r\n{code}\r\n\r\nFull test case: \r\n\r\n{code}\r\nvar win = Ti.UI.createWindow({\r\n\tbackgroundColor:'white',\r\n});\r\n\r\nvar imageView = Ti.UI.createImageView({\r\n\timage:null,\r\n});\r\n\r\nfunction loadImage() {\r\n var url = \"http://dummyimage.com/600x400/\" + getRandomColor() + \"/\" + getRandomColor() + \".jpg&text=Hello\";\r\n Ti.API.info(url);\r\n imageView.image = null;\r\n imageView.image = url;\r\n}\r\n \r\nfunction getRandomColor() {\r\n var letters = '0123456789abcdef'.split('');\r\n var color = '';\r\n for (var i = 0; i < 6; i++ ) {\r\n color += letters[Math.floor(Math.random() * 16)];\r\n }\r\n return color;\r\n}\r\n \r\nwin.open();\r\nwin.add(imageView);\r\nloadImage();\r\n \r\nsetInterval(function() {\r\n loadImage();\r\n}, 1000);\r\n{code}\r\n\r\nBest Regards", "updateAuthor": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "created": "2014-07-01T16:36:44.000+0000", "updated": "2014-07-01T16:36:44.000+0000" }, { "id": "311856", "author": { "name": "cbowley", "key": "cbowley", "displayName": "Chris Bowley", "active": true, "timeZone": "Europe/London" }, "body": "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.\r\n\r\nFor 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.\r\n\r\nUse 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.\r\n\r\nChris\r\n\r\n", "updateAuthor": { "name": "cbowley", "key": "cbowley", "displayName": "Chris Bowley", "active": true, "timeZone": "Europe/London" }, "created": "2014-07-01T18:14:40.000+0000", "updated": "2014-07-01T18:17:17.000+0000" }, { "id": "311879", "author": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "body": "Hello Chris!\r\n\r\nI 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 \r\n\r\nFrom your screenshot, that is happening in iOS8. iOS 8 is not final yet, and likely to contain bugs (like a faulty GC). \r\n\r\nBest Regards \r\n", "updateAuthor": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "created": "2014-07-01T19:22:15.000+0000", "updated": "2014-07-01T19:22:15.000+0000" }, { "id": "311901", "author": { "name": "cbowley", "key": "cbowley", "displayName": "Chris Bowley", "active": true, "timeZone": "Europe/London" }, "body": "Hi Mauro\r\n\r\nI'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.\r\n\r\nI'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.\r\n\r\n", "updateAuthor": { "name": "cbowley", "key": "cbowley", "displayName": "Chris Bowley", "active": true, "timeZone": "Europe/London" }, "created": "2014-07-01T20:55:24.000+0000", "updated": "2014-07-01T20:55:24.000+0000" }, { "id": "311914", "author": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "body": "Hello [~cbowley], \r\n\r\nhere is my project:\r\n\r\nhttps://drive.google.com/file/d/0B439umN-AOyxRHpyTnBybW9fM0U/edit?usp=sharing\r\n\r\nBest Regards", "updateAuthor": { "name": "mpmiranda", "key": "mpmiranda", "displayName": "Mauro Parra-Miranda", "active": true, "timeZone": "America/Mexico_City" }, "created": "2014-07-01T21:30:30.000+0000", "updated": "2014-07-01T21:30:30.000+0000" }, { "id": "311924", "author": { "name": "cbowley", "key": "cbowley", "displayName": "Chris Bowley", "active": true, "timeZone": "Europe/London" }, "body": "Mauro\r\n\r\nIt 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.\r\n\r\nChris", "updateAuthor": { "name": "cbowley", "key": "cbowley", "displayName": "Chris Bowley", "active": true, "timeZone": "Europe/London" }, "created": "2014-07-01T21:53:27.000+0000", "updated": "2014-07-01T21:53:27.000+0000" } ], "maxResults": 7, "total": 7, "startAt": 0 } } }