How would you resolve the following memory leak?
I am trouble shooting memory leaks in an iOS app Ti SDK 6.2 without much success.
When opening and closing windows the xcode Instruments Allocations tool shows a number of TiUIxxxxProxy objects remain in memory.
To test/debug the issue I created a super simple classic appcelerator app (code shown below). Using the code below I open window1 from app.js, then open window2 from a button on window1.
You can see in the attached xcode Instruments Allocations images that after window2 is closed the proxy objects remain (window, table, etc). Worse yet, opening and closing window2 multiple times keeps adding additional proxy objects that use memory.
App.js
require('Window1').CreateWindow().open();
Window1.js
exports.CreateWindow = function(){
try{
var window1 = Ti.UI.createWindow({
title:'Window 1',backgroundColor:'yellow',
});
var button1 = Ti.UI.createButton({
top:'50dp', center:'50%',
borderWidth:'1dp',borderColor:'black',
title:' Open Window 2 '
});
window1.add(button1);
button1.addEventListener('click', function() {
var win2 = require('Window2').CreateWindow();
win2.open();
});
return window1;
}
catch(e){
alert('Window 1 Error: ' + e);
}
};
Window2.js
exports.CreateWindow = function(){
try{
var window2 = Ti.UI.createWindow({
title:'Window 2',layout:'vertical'
,top:'200dp',bottom:'200dp',width:'80%'
,backgroundColor:'orange'
});
var button2 = Ti.UI.createButton({
top:'50dp', center:'50%',
borderWidth:'1dp',borderColor:'black',
title:' Close Window 2 '
});
window2.add(button2);
button2.addEventListener('click', function() {
window2.close();
});
// create a table row
var tvRow1 = Ti.UI.createTableViewRow({ });
//create a label to display location name
var labelRow1 = Ti.UI.createLabel({
color:'#000000',
left:'15dp',
top:'10dp',
text:'Label in row 1'
});
tvRow1.add(labelRow1);
// define table section for this group of rows
var tableViewSection1 = Ti.UI.createTableViewSection({ });
// push row into table view section
tableViewSection1.add(tvRow1);
//create array to store table sections
var arrayTableSections = [];
//add table section to array
arrayTableSections.push(tableViewSection1);
// create table
var table2 = Titanium.UI.createTableView({
data: arrayTableSections,
top:'50dp',
left:'50dp',
right:'50dp',
height:Ti.UI.SIZE,
backgroundColor:'#ffffff' ,
borderColor:'black',borderWidth:'1dp'
});
// add table to window
window2.add(table2);
return window2;
}
catch(e){
alert('Window 2 Error: ' + e);
}
};
The specific problem you're having is that you're manually creating elements and not cleaning up. So, on the close of Window2 you should be removing all elements from their parents, and ultimately from Window2 so:
remove the label from the row
remove the row from the section
remove the section from the table
remove the table from the window
remove the button from the window
then
nullify the label, the row, the section, the table and the button
and most importantly:
clear the pointer to the array of sections / rows.
Once done, ensure you have a removeEvenlistener in close to remove the event handler too.
Finally, close the window and nullify it.
One last thing, don't create a window2 pointer in window1 if you're not going to need it later so:
require("window2").createWindow().open();
is better than:
var window2 = require("window2").createWindow();
window2.open();
Doing that I was able to open window2 8+ times, and it all cleaned up afterwards.
Related
I have this code https://jsfiddle.net/delux123/62Lmskbx/11/ where user can draw circle, segment and arrow-segment. When each annotation is selected, there is a "delete" button available, which should normally delete the selected annotation.
But this does not works! I tried the following ways for removing them:
1 ) using thischart.currentAnnotation.destroy() will delete the annotation (works for all three types) but then all the further actions stops from working
document
.querySelectorAll('.highcharts-popup-annotations .deletebutton')[0]
.addEventListener(
'click',
function() {
thischart.currentAnnotation.destroy(); //this stops further drawing
thischart.annotationsPopupContainer.style.display = 'none';
}
);
2 ) using deletion by ID thischart.removeAnnotation(thischart.currentAnnotation.options.id) does not works, since the annotations are created dynamically and they do not have IDs assigned to them.
document
.querySelectorAll('.highcharts-popup-annotations .deletebutton')[0]
.addEventListener(
'click',
thischart.removeAnnotation(thischart.currentAnnotation.options.id); //this requires elements to have IDs (which they do not have)
thischart.annotationsPopupContainer.style.display = 'none';
}
);
For the second approach, I even tried to intersect the drawing and to assign a random string as an ID (since the reason that deletion by id does not works, is because the ID is undefined). So under navigation -> bindings I added the object:
circleAnnotation: {
start: function(e) {
var navigation = this.chart.options.navigation;
return this.chart.addAnnotation(
Highcharts.merge({
id: randomStr() //this is a method that generates random string
},
navigation
.annotationsOptions,
navigation
.bindings
.circleAnnotation
.annotationsOptions
)
);
}
}
The both approaches are not working.
You can pass a direct annotation object to the removeAnnotation method:
thischart.removeAnnotation(thischart.currentAnnotation);
Live demo: https://jsfiddle.net/BlackLabel/5ycf3s4o/
API Reference: https://api.highcharts.com/class-reference/Highcharts.Chart#removeAnnotation
this.grid = new Grid<>(Person.class);
this.grid.setItems(personList);
this.grid.setSelectionMode(SelectionMode.MULTI);
this.grid.removeAllColumns();
this.grid.setColumns("firstname");
this.editButton = new Button(null, ImageIcons.EDIT.create());
this.editButton.getStyle().set("color", "#000000");
this.grid.addComponentColumn(person -> this.editButton);
this.deleteButton = new Button(null, IronIcons.DELETE_FOREVER.create());
this.deleteButton.getStyle().set("color", "#000000");
this.grid.addComponentColumn(person -> this.deleteButton);
this.addComponentAsFirst(this.grid);
I have a personList with several entries. The grid shows all these entries with their first name. But it only shows the buttons in the last row. What is the problem?
You use the very same Button instance for each row. You should create a new Button within the componentRenderer, so each row will have its own Button.
Try it like this:
this.grid = new Grid<>(Person.class, false);
this.grid.setItems(personList);
this.grid.setSelectionMode(SelectionMode.MULTI);
this.grid.setColumns("firstname");
this.grid.addComponentColumn(person -> {
// edit: added click listener for inline-editing of the person. Editor must be configured for this to work. See https://vaadin.com/components/vaadin-grid/java-examples/grid-editor
// You don't have to use inline-editing if you don't want. you can also edit the item in a separate Layout with Input fields and a Binder.
Button editButton = new Button(ImageIcons.EDIT.create(), click -> {
this.grid.getEditor().editItem(person);
});
editButton.getStyle().set("color", "#000000");
return editButton;
});
this.grid.addComponentColumn(person -> {
// edit: added click listener for person removal
Button deleteButton = new Button(null, IronIcons.DELETE_FOREVER.create(), click -> {
this.personDao.remove(person);
// TODO: when using an in-memory dataprovider, fetch all items again from service/dao and set them with grid.setItems(this.personDao.findAll());
// but that is not necessary when using a callback dataprovider, which I belive OP is using
this.grid.getDataProvider().refresh();
});
deleteButton.getStyle().set("color", "#000000");
return deleteButton;
}
this.addComponentAsFirst(this.grid);
Edit: a minor thing but I still wanted to mention it - You do some unnecessary creation of columns, only to remove them all again later. Instead of this you could tell the grid not to create these columns in the first place, by passing false as second parameter of the Grid constructor.
this.grid = new Grid(Person.class, false);
// instead of
this.grid = new Grid(Person.class);
this.grid.removeAllColumns();
I got this piece of code for rendering and using Flipswitch as a custom control in lightswitch application.
function createBooleanSwitch(element, contentItem, trueText, falseText, optionalWidth) {
var $defaultWidth = '5.4em';
var $defaultFalseText = 'False';
var $defaultTrueText = 'False';
var $selectElement = $('<select data-role="slider"></select>').appendTo($(element));
if (falseText != null) {
$('<option value="false">' + falseText + '</option>').appendTo($selectElement);
}
else {
$('<option value="false">' + $defaultFalseText + '</option>').appendTo($selectElement);
}
if (trueText != null) {
$('<option value="true">' + trueText + '</option>').appendTo($selectElement);
}
else {
$('<option value="true">' + $defaultTrueText + '</option>').appendTo($selectElement);
}
// Now, after jQueryMobile has had a chance to process the
// new DOM addition, perform our own post-processing:
$(element).one('slideinit', function () {
var $flipSwitch = $('select', $(element));
// Set the initial value (using helper function below):
setFlipSwitchValue(contentItem.value);
// If the content item changes (perhaps due to another control being
// bound to the same content item, or if a change occurs programmatically),
// update the visual representation of the control:
contentItem.dataBind('value', setFlipSwitchValue);
// Conversely, whenver the user adjusts the flip-switch visually,
// update the underlying content item:
$flipSwitch.change(function () {
contentItem.value = ($flipSwitch.val() === 'true');
});
// To set the width of the slider to something different than the default,
// need to adjust the *generated* div that gets created right next to
// the original select element. DOM Explorer (F12 tools) is a big help here.
if (optionalWidth != null) {
$('.ui-slider-switch', $(element)).css('width', optionalWidth);
}
else {
$('.ui-slider-switch', $(element)).css('width', defaultWidth);
}
//===============================================================//
// Helper function to set the value of the flip-switch
// (used both during initialization, and for data-binding)
function setFlipSwitchValue(value) {
$flipSwitch.val((value) ? 'true' : 'false');
// Having updated the DOM value, refresh the visual representation as well
// (required for a slider control, as per jQueryMobile's documentation)
$flipSwitch.slider(); // Initializes the slider
$flipSwitch.slider('refresh');
// Because the flip switch has no concept of a "null" value
// (or anything other than true/false), ensure that the
// contentItem's value is in sync with the visual representation
contentItem.value = ($flipSwitch.val() === 'true');
}
});
}
This piece of code works fine. It renders the flipswitch on the screen. I am showing the data in an Edit screen, which is coming in a popup. Problem arises when I open that popup which contains the flipswitch and without changing any data on UI, I just try to close that popup screen. The IE hangs and it gives error saying that long script is running. When I debug the createBoolenaSwitch function, I came to know that it is going in infinite loop inside the function called setFlipSwitchValue(value){}
Why is this function getting called and this is going in an infinite loop?
I have a Kendo window which is defined as follows:
With Html.Kendo().Window().Name("tranferwindow")
.Title("Select Transfer Destination")
.Content("")
.Resizable()
.Modal(True)
.Events(Function(events) events.Open("WindowToCenter"))
.Events(Function(events) events.Refresh("transferopen"))
.Draggable()
.Width(400)
.Visible(False)
.Render()
End With
The window is opened each time by using the refresh and passing a new URL.This is to allow dynamic data to be displayed dependent on what the user clicked on a grid.
function transferitem(e) {
var dataItem = this.dataItem($(e.currentTarget).closest("tr"));
wwindow.data("kendoWindow").open(); //Display waiting window while refresh happens
var twindow = $("#tranferwindow")
twindow.data("kendoWindow").refresh('/Home/TransferList?agentid=' + agentid + '&tenantid=' + tenantid + '&SessionID=' + dataItem.MediaID);
}
The Window is opened at the end of the refresh event to make sure the user doesn't see the previous content.
function transferopen() {
wwindow.data("kendoWindow").close(); //Close the 'wait' window
var twindow = $("#tranferwindow")
twindow.data("kendoWindow").center().open();
}
This all works well and the window can be closed and reopened as often as I like.
However I needed to access the resize event of the window from within the Partial View to resize the Grid which is inside the window. To achieve this I added the following to the partial view that is returned from the url.
$("#tranferwindow").kendoWindow({
resize: function (e) {
// resizeGrid();
}
});
Adding this event mapping causes the issue where I cannot open the Window more than once.
I assume I need to 'unregister' for the event somehow before closing?
Found a solution: much cleaner and no VB Razor needed :)
I changed the approach to create a new window each time I wanted to display one.
I created a div to hold the Window.
<div id="windowcontainer"></div>
Then when the user selected a command on the grid , I create the whole window appending it to the div. The key here is the this.destroy in the deactivate event.
function transferitem(e) {
var dataItem = this.dataItem($(e.currentTarget).closest("tr"));
$("#windowcontainer").append("<div id='tranferwindow'></div>");
var mywindow = $("#tranferwindow")
.kendoWindow({
width: "400px",
title: "Select Transfer Destination",
visible: false,
content: '/Home/TransferList?agentid=' + agentid + '&tenantid=' + tenantid + '&SessionID=' + dataItem.MediaID,
deactivate: function () {
this.destroy();
},
open: WindowToCenter,
refresh:transferopen
}).data("kendoWindow");
mywindow.refresh();
}
Then on the refresh function
function transferopen() {
var twindow = $("#tranferwindow")
twindow.data("kendoWindow").center().open();
}
Now I can have the event binding inside the Partial View which works fine and the window can be re opened as many times as I want. :)
Update: Adding the event binding inside the Partial View stops 'Modal' from working. Working on trying to fix this...
I want to be selected that new Item in list when I give new value to dataProvider but it selects the item that was firstly selected, and how to make that selected permanently on App start up.
You if want to select the list item after added to dataProvider. Probably you follow this
list.selectedIndex = list.dataProvider.length -1;
Sometimes if you added at desired position like
list.dataProvider.setItemAt(obj,2);
list.selectedIndex = 2
If you want to selected item permanently on App start up.(Make sure that your array collections bind-able to list)
public function onCreationComplete(event:FlexEvent):void
{
callLater(function():void
{
list.selectedIndex = list.dataProvider.length -1;
list.ensureIndexIsVisible(list.selectedIndex); // Viewport
});
}