[TIMOB-3572] Updating views on a ScrollableView does not release previously set views from memory..
GitHub Issue | n/a |
---|---|
Type | Bug |
Priority | Medium |
Status | Closed |
Resolution | Fixed |
Resolution Date | 2011-05-18T15:12:59.000+0000 |
Affected Version/s | n/a |
Fix Version/s | Sprint 2011-16 |
Components | iOS |
Labels | enterprise, ios |
Reporter | Fred Spencer |
Assignee | Blain Hamon |
Created | 2011-04-15T03:46:46.000+0000 |
Updated | 2011-05-18T15:12:59.000+0000 |
Description
Expectation:
var scrollable = Ti.UI.createScrollableView();
scrollable.views = [set of views]; // initial
scrollable.views = [new set of views]; // further in flow; release previous set of views
Steps to recreate:
1) Run on device and load Instruments (all processes)
2) Tap 'Generate Section' button to confirm memory is being
released on recreation.
3) Tap 'Update Views' button to confirm memory is not being
released. Application will send memory notifications and eventually
crash on approx. 15-20 attempts.
var win = Ti.UI.createWindow({ backgroundColor:'#fff', top:0, right:0, bottom:0, left:0 });
var contentContainer = Ti.UI.createView({ backgroundColor:'#000' });
var sectionContainer = Ti.UI.createView({ backgroundColor:'#f00' });
var scrollable = Ti.UI.createScrollableView({ top:0, right:0, left:0, bottom:50, backgroundColor:'#ff0' });
var button1 = Ti.UI.createButton({ bottom:0, left:0, width:'40%', height:50, title:'GENERATE SECTION' });
var button2 = Ti.UI.createButton({ bottom:0, right:0, width:'40%', height:50, title:'UPDATE VIEWS' });
var sectionState = false;
var currentImages = [];
function createButton() {
var button = Ti.UI.createButton({ left:0, right:0, height:30, bottom:0, title:'Button' });
button.addEventListener('click', function() {
Ti.API.info('Tapped button.');
});
return button;
}
// ### Border Radius Performance - toImage improves this - larger memory footprint
function generateItem(img) {
var itemContainer = Ti.UI.createView({ width:200, height:200, top:5, left:5, bottom:5, right:5, backgroundColor:'#0f0' });
var image = Ti.UI.createImageView({ width:200, height:100, image:img, borderRadius:5 }); // slow performance
var comp = Ti.UI.createImageView({ width:200, height:100 });
var title = Ti.UI.createLabel({ touchEnabled:'false', text:'Title', textAlign:'center', top:0, height:'auto' });
var button = createButton();
itemContainer.add(image);
itemContainer.add(title);
itemContainer.add(button);
itemContainer.addEventListener('singletap', function() {
Ti.API.info('Tapped view.');
});
image.addEventListener('load', function() {
comp.image = image.toImage();
itemContainer.remove(image);
itemContainer.add(comp);
});
return itemContainer;
}
function createCustomView(images) {
var view = Ti.UI.createView({ top:0, left:0, right:0, bottom:0, layout:'horizontal', backgroundColor:'#f00' });
var item;
for (var i = 0; i < 12; i++) {
item = generateItem(images[i]);
view.add(item);
}
item.addEventListener('singletap', function() {
Ti.API.info('Tapped view.');
});
return view;
}
// SUCCESS ON RELEASE FROM MEMORY
function generateSection() {
if (sectionState) {
contentContainer.remove(sectionContainer);
}
setTimeout(function() {
var images = [];
for (var i = 0; i < 12; i++) {
images.push('image.png');
}
currentImages = images;
sectionContainer = Ti.UI.createView({ backgroundColor:'#f00' });
contentContainer.add(sectionContainer);
scrollable = Ti.UI.createScrollableView({ top:0, right:0, left:0, bottom:50, backgroundColor:'#ff0' });
scrollable.views = [
createCustomView(images),
createCustomView(images),
createCustomView(images),
createCustomView(images)
];
sectionContainer.add(scrollable);
sectionState = true;
}, 500);
}
// MEMORY ISSUES
function refreshViews() {
// works on simulator, crash on device (log below) - perhaps this should be done in a reverse order (remove last, first)
// for (var i = 0, sl = scrollable.views.length; i < sl; i++) {
// scrollable.removeView(scrollable.views[i]);
// }
// [ERROR] The application has crashed with an unhandled exception. Stack trace:
//
// 0 CoreFoundation 0x3173463d __exceptionPreprocess + 96
// 1 libobjc.A.dylib 0x3642dc5d objc_exception_throw + 24
// 2 CoreFoundation 0x31734491 +[NSException raise:format:arguments:] + 68
// 3 CoreFoundation 0x317344cb +[NSException raise:format:] + 34
// 4 QuartzCore 0x309ed61d _ZL18CALayerSetPositionP7CALayerRKN2CA4Vec2IdEEb + 140
// 5 QuartzCore 0x309ed58b -[CALayer setPosition:] + 38
// 6 UIKit 0x32e1136b -[UIView(Geometry) setCenter:] + 22
// 7 scrollabletest2 0x00043115 -[TiViewProxy relayout] + 632
// 8 scrollabletest2 0x00042673 -[TiViewProxy refreshView:] + 362
// 9 scrollabletest2 0x00043445 -[TiViewProxy layoutChildrenIfNeeded] + 136
// 10 scrollabletest2 0x0009d459 performLayoutRefresh + 344
// 11 CoreFoundation 0x3170ba47 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 14
// 12 CoreFoundation 0x3170decb __CFRunLoopDoTimer + 850
// 13 CoreFoundation 0x3170e845 __CFRunLoopRun + 1088
// 14 CoreFoundation 0x3169eec3 CFRunLoopRunSpecific + 230
// 15 CoreFoundation 0x3169edcb CFRunLoopRunInMode + 58
// 16 GraphicsServices 0x3288741f GSEventRunModal + 114
// 17 GraphicsServices 0x328874cb GSEventRun + 62
// 18 UIKit 0x32e07d69 -[UIApplication _run] + 404
// 19 UIKit 0x32e05807 UIApplicationMain + 670
// 20 scrollabletest2 0x00003bb3 main + 70
// 21 scrollabletest2 0x000036a8 start + 40
//
//
// 2011-04-13 09:19:00.904 scrollabletest2[8820:707] *** Terminating app due to uncaught exception 'CALayerInvalidGeometry', reason: 'CALayer position contains NaN: [nan 4.0726e-10]'
// *** Call stack at first throw:
// (
// 0 CoreFoundation 0x3173464f __exceptionPreprocess + 114
// 1 libobjc.A.dylib 0x3642dc5d objc_exception_throw + 24
// 2 CoreFoundation 0x31734491 +[NSException raise:format:arguments:] + 68
// 3 CoreFoundation 0x317344cb +[NSException raise:format:] + 34
// 4 QuartzCore 0x309ed61d _ZL18CALayerSetPositionP7CALayerRKN2CA4Vec2IdEEb + 140
// 5 QuartzCore 0x309ed58b -[CALayer setPosition:] + 38
// 6 UIKit 0x32e1136b -[UIView(Geometry) setCenter:] + 22
// 7 scrollabletest2 0x00043115 -[TiViewProxy relayout] + 632
// 8 scrollabletest2 0x00042673 -[TiViewProxy refreshView:] + 362
// 9 scrollabletest2 0x00043445 -[TiViewProxy layoutChildrenIfNeeded] + 136
// 10 scrollabletest2 0x0009d459 performLayoutRefresh + 344
// 11 CoreFoundation 0x3170ba47 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 14
// 12 CoreFoundation 0x3170decb __CFRunLoopDoTimer + 850
// 13 CoreFoundation 0x3170e845 __CFRunLoopRun + 1088
// 14 CoreFoundation 0x3169eec3 CFRunLoopRunSpecific + 230
// 15 CoreFoundation 0x3169edcb CFRunLoopRunInMode + 58
// 16 GraphicsServices 0x3288741f GSEventRunModal + 114
// 17 GraphicsServices 0x328874cb GSEventRun + 62
// 18 UIKit 0x32e07d69 -[UIApplication _run] + 404
// 19 UIKit 0x32e05807 UIApplicationMain + 670
// 20 scrollabletest2 0x00003bb3 main + 70
// 21 scrollabletest2 0x000036a8 start + 40
// )
// terminate called after throwing an instance of 'NSException'
// this seems to work, but memory jumps on regenerating section
// for (var i = 0, sl = scrollable.views.length; i < sl; i++) {
// scrollable.views[i] = createCustomView(currentImages);
// }
// previous views do not seem to be released from memory
scrollable.views = [
createCustomView(currentImages),
createCustomView(currentImages),
createCustomView(currentImages),
createCustomView(currentImages)
];
}
button1.addEventListener('click', function(e) {
generateSection();
});
button2.addEventListener('click', function(e) {
refreshViews();
});
win.add(contentContainer);
win.add(button1);
win.add(button2);
win.open();
generateSection();
Attachments
File | Date | Size |
---|---|---|
image.png | 2011-04-15T03:46:47.000+0000 | 27324 |
Image used for example.
ios_proxy_registration
commit: 6c9e23fb5ae2b9d838e841217193964ef4bd274a
Right now setViews is rather broken in that it doesn't release the old proxies, it's true. Added to that, addView and removeView (Despite handling proxy retention properly) don't update the views property. Unfortunately, the cause lies somewhat deeper, involving the proxies owned by the view instead of the scrollableviewproxy, which makes fixing this sticky.
In the mean time, there's two solutions:
1) Treat setViews as a write-once action, and don't recycle a scrollableView to use one collection, then run over and use a different collection; use multiple scrollableViews.
2) If the former is not an option, the following can be done as a workaround until scrollableView is fixed, given scrollableView foo:
Confirmed that this provides fix for second workaround. Memory also appears to be releasing properly. Updated source (image attached, above):
Warnings on pressing 'Update Views' button. [WARN] Nil view frame was requested for [object TiUIView] in -[TiViewProxy refreshView:] (TiViewProxy.m:1426) [WARN] Nil view frame was requested for [object TiUIView] in -[TiViewProxy refreshView:] (TiViewProxy.m:1426) [WARN] Nil view frame was requested for [object TiUIView] in -[TiViewProxy refreshView:] (TiViewProxy.m:1426) [WARN] Nil view frame was requested for [object TiUIView] in -[TiViewProxy refreshView:] (TiViewProxy.m:1426) [WARN] Nil view frame was requested for [object TiUIView] in -[TiViewProxy refreshView:] (TiViewProxy.m:1426) [WARN] Nil view frame was requested for [object TiUIView] in -[TiViewProxy refreshView:] (TiViewProxy.m:1426)
Verified in instruments, the old imageviews are getting purged. Hooray.
Hi Blain, is this behaviour: http://developer.appcelerator.com/question/119465/ios-regression-170-using-more-than-3-views-inside-a-scrollableview-ends-in-blank-tableviews as expected now after working on the leak management or is it a regression? Worked in SDK 1.6.1 but no longer in 1.7.0
Used revised test code, instruments reports a initial state of 1.76MB. Repeatedly tapping on "Update Views" varied the usage, generally alternating between 2.3x and 1.8x MB. Closing. Note: the revised test code will crash after about 6 taps on the button with an NS exception that may be caused by trying to remove objects that are already removed. We will port the code to a test app and modify when we do to prevent this if possible.