RE: Memory Consumption in App

I am working on a sizeable app in Fuse and I have been running into issues with it crashes on my device and emlator on load. I am creating a number of JS Objects (Models) with many Observable properties and there seems to be a limit where it causes the app to crash. I haven’t tried lazy loading my data yet, I just have it hard coded as an example and once I get to 40 or so objects created, I run out of memory.

So my question is this… what could be a better way of organizing this data. I ran into binding issues when all of the properties weren’t Observables, but maybe that’s a lot of overhead I don’t need and there is a better way to do it? I bind most of these properties to my views as I list out the businesses and then drill in for more detail.

Below is an example of my Business.js object I’m working with. Once I get to about 40 items, it crashes with out of memory errors. It maxes out around 640 MB on my iPhone 5.

var Observable = require('FuseJS/Observable');

function BusinessBase() {
  this.BusinessID =                 Observable();
  this.SmallImageID =               Observable();
  this.Business_Website =           Observable();
  this.UserSelected =               Observable();
  this.Business_PhoneNumber =       Observable();
  this.Address1 =                   Observable();
  this.Address2 =                   Observable();
  this.City =                       Observable();
  this.State =                      Observable();
  this.ZipCode =                    Observable();
  this.Business_ShortDescription =  Observable();
  this.Business_LongDescription =   Observable();
  this.Latitude =                   Observable();
  this.Longitude =                  Observable();
  this.SortCategory =               Observable();
  this.Trending =                   Observable();
  this.BusinessImageVC =            Observable("");

  this.Business_Name =              Observable();
  this.Business_Category =          Observable();
  this.Business_SubCategory =       Observable();
  this.DistanceMiles =              Observable();
  this.businessLogo =               "OfferLogo";

  this.set = function(obj) {
    if(obj.BusinessID) this.BusinessID.value =                                obj.BusinessID;
    if(obj.Business_Name) this.Business_Name.value =                          obj.Business_Name;
    if(obj.Business_ShortDescription) this.Business_ShortDescription.value =  obj.Business_ShortDescription;
    if(obj.UserSelected) this.UserSelected.value =                            obj.UserSelected == 1 ? true : false;
    if(obj.Distance) this.DistanceMiles.value =                               Number(obj.Distance).toFixed(1);
    if(obj.Business_Catagory) this.Business_Category.value =                  obj.Business_Catagory;
    if(obj.Business_SubCatagory) this.Business_SubCategory.value =            obj.Business_SubCatagory;
    if(obj.Business_Website) this.Business_Website.value =                    obj.Business_Website;
    if(obj.SmallImageID) this.SmallImageID.value =                            obj.SmallImageID;
    if(obj.Latitude) this.Latitude.value =                                    obj.Latitude;
    if(obj.Longitude) this.Longitude.value =                                  obj.Longitude;
    if(obj.SortCategory) this.SortCategory.value =                            obj.SortCategory;
    if(obj.Business_LongDescription) this.Business_LongDescription.value =    obj.Business_LongDescription;
    if(obj.Business_PhoneNumber) this.Business_PhoneNumber.value =            obj.Business_PhoneNumber;
    if(obj.Address1) this.Address1.value =                                    obj.Address1;
    if(obj.Address2) this.Address2.value =                                    obj.Address2;
    if(obj.City) this.City.value =                                            obj.City;
    if(obj.State) this.State.value =                                          obj.State;
    if(obj.ZipCode) this.ZipCode.value =                                      obj.ZipCode;
    if(obj.Trending) this.Trending.value =                                    obj.Trending;
    if(obj.ImageVC) this.BusinessImageVC.value =                              obj.ImageVC;

    this.businessLogo =                                                       "OfferLogo";
  }
}

function GetBusinessFromID(id, businessList) {
  for(var i = 0; i < businessList.length; i++) {
    var business = businessList.getAt(i);

    if(business && business.BusinessID && business.BusinessID.value == id) {
      return business;
    }
  }

  return null;
}

function BuildBusinesses(rawObj, businessList, sortBy) {
  debug_log("BEGIN BuildBusinesses()");
  debug_log("rawObj.length: " + rawObj.length);

  var tempArray = [];

  for(var i = 0; i < rawObj.length; i++) {
    tempArray.push(buildBusiness(rawObj[i]));
  }

  SortBusinessList(sortBy, tempArray);

  businessList.replaceAll(tempArray);

  debug_log("END BuildBusinesses()");
}

function buildBusiness(rawObj) {
  var business = new BusinessBase();

  business.set(rawObj);

  return business;
}

function SortBusinessList(sortBy, tempArray) {

  tempArray.sort(sortMethod);

  function sortMethod(v1, v2) {
    if(v1[sortBy.value] < v2[sortBy.value]) return -1;
    if(v1[sortBy.value] > v2[sortBy.value]) return 1;
    return 0;
  }
}

module.exports = {
  BusinessBase: BusinessBase,
  GetBusinessFromID: GetBusinessFromID,
  BuildBusinesses: BuildBusinesses,
  SortBusinessList: SortBusinessList
};

In addition, I have many other views I’ve removed from my project to test this, and the only thing that makes a large difference in memory is how these objects are being created and handled. I can add 40 new views and not change the amount of data I’m creating here and the memory footprint barely moves. That leads me to beleive it’s this and not the construction of my views in UX (which makes me feel really good that the UX is very efficient).

Then I call…

Business.BuildBusinesses(tempBusinesses, businessList, businessSortBy);

With an array of business data, the Observable I’m filling with my objects and a column to sort by.

Thanks for the diliberate test case.

I don’t think the JS objects is the issue, there is probably some leak on the Uno side that we need to fix :slight_smile: We’ll look into it!

Here is a couple test businesses for data you can just replicate until you see the memory issues.

var tempBusinesses = [
    {"Business_Catagory":"Business & Personal Services","Business_SubCatagory":"Marketing & Sales","Business_Website":"www.google.com","Business_Name":"Test Business","Business_DateAdded":"10/1/2014 12:06:00 AM","UserID":"1","UserSelected":"0","BusinessID":"6","SmallImageID":"77","Business_ShortDescription":"For Live Testing Purposes Only","Latitude":"40.584670","Longitude":"-92.650482","SortID":"200016","Business_ModifiedDate":"9/18/2015 1:22:00 PM","SortCategory":"2","Rank":"16","Lovers":"0","Distance":"2.41771775653806","Popularity":"1","IsNew":"0","StoreType":"Fixed","IncludedFilters":"|Services|Offers|Around Me|Trending|Bookmarks|","Trending":"2"},
    {"Business_Catagory":"Business & Personal Services","Business_SubCatagory":"Marketing & Sales","Business_Website":"www.google.com","Business_Name":"Test Business","Business_DateAdded":"10/1/2014 12:06:00 AM","UserID":"1","UserSelected":"0","BusinessID":"6","SmallImageID":"77","Business_ShortDescription":"For Live Testing Purposes Only","Latitude":"42.584714","Longitude":"-94.650508","SortID":"200016","Business_ModifiedDate":"9/18/2015 1:22:00 PM","SortCategory":"2","Rank":"16","Lovers":"0","Distance":"2.41754298140767","Popularity":"1","IsNew":"0","StoreType":"Fixed","IncludedFilters":"|Services|Offers|Around Me|Trending|Bookmarks|","Trending":"2"}
];

Honestly all those properties most likely don’t have to be an observable, but I don’t fully know unless I can see how that connects to your UI, and you’re exposing to the UI each function, so I’m not sure what’s calling some of them (which you probably don’t even call them from UI so need to put them in the module.exports)

But I’ve refactored your code (I declared the properties so you at least know what properties each object should have, but didn’t give them any value, I don’t think they need to be Observables):

var Observable = require('FuseJS/Observable');

function BusinessBase() {
    this.BusinessID;
    this.SmallImageID;
    this.Business_Website;
    this.UserSelected;
    this.Business_PhoneNumber;
    this.Address1;
    this.Address2;
    this.City;
    this.State;
    this.ZipCode;
    this.Business_ShortDescription;
    this.Business_LongDescription;
    this.Latitude;
    this.Longitude;
    this.SortCategory;
    this.Trending;
    this.BusinessImageVC;

    this.Business_Name;
    this.Business_Category;
    this.Business_SubCategory;
    this.DistanceMiles;
    this.businessLogo = 'OfferLogo';

    this.set = function(obj) {
        for (var key in obj) {
            this[key] = obj[key];
        }

        if (obj.UserSelected) this.UserSelected.value = obj.UserSelected == 1 ? true : false;
        if (obj.Distance) this.DistanceMiles.value = Number(obj.Distance).toFixed(1);
    }
}

function GetBusinessFromID(id, businessList) {
    // find method is part of ES6 so you may not be able to use that method, not sure if it'll run on IOS
    return businessList.find(function(business) {
        return business.BusinessID && business.BusinessID.value == id;
    }) || null;

    // Version you can use if you can't use ES6:
    return businessList.filter(function(business) {
        return business.BusinessID && business.BusinessID.value == id;
    })[0] || null;
}

function BuildBusinesses(rawObj, businessList, sortBy) {
    debug_log("BEGIN BuildBusinesses()");
    debug_log("rawObj.length: " + rawObj.length);

    var tempArray = rawObj.map(buildBusiness);

    SortBusinessList(sortBy, tempArray);

    businessList.replaceAll(tempArray);

    debug_log("END BuildBusinesses()");
}

function buildBusiness(rawObj) {
    var business = new BusinessBase();

    business.set(rawObj);

    return business;
}

function SortBusinessList(sortBy, tempArray) {

    tempArray.sort(sortMethod);

    function sortMethod(v1, v2) {
        if (v1[sortBy.value] < v2[sortBy.value]) return -1;
        if (v1[sortBy.value] > v2[sortBy.value]) return 1;
        return 0;
    }
}

module.exports = {
    BusinessBase: BusinessBase,
    GetBusinessFromID: GetBusinessFromID,
    BuildBusinesses: BuildBusinesses,
    SortBusinessList: SortBusinessList
}

Cool, I’ll try this out. I was running into issues with the properties I’ve bound to Text elements, etc. not updating unless they were Observables, but we’ll see how this goes. Maybe I just need to be selective of which ones I need as Observables for binding purposes and which ones I don’t. That would probably help.

I’ll remove them all and then slowly turn them back and see how my memory footprint fares.

Thanks!

I am now creating 45 business objects with only one of the properties being an Observable now; all the others are not. I’ve included your refactors as well.

It still gets up to 600MB of memory before it starts to GC.

If I add any other objects into the mix, even after I’ve trimmed them down like this business object, it pushes it over 600MB and crashes on my iPhone 5.

It doesn’t seem like 50 or so js objects of this size should completely wreck it, should it? I also have a lot of views, but again the process of creating these objects seems to take the most resources on the device.

Any more thoughts?

Hi,

This makes no sense at all, from the code you have been showing.

If you run just your JS logic (just remove all the views), does it still use all that RAM?

So I removed all Views except for a the basic list components that display these items. At about 80-90 items, my memory is up to about 350 MB and then GC kicks in. (in my “whole” project I can’t even load 50 items before it hits 620 MB and crashes)

In my “whole” app project, I am using a lot of Observables to control visibility. I am wondering though if a better architecture would be to have one observable that has a list of visiblity values in it and control it from those. It seems like the more I can remove Observables from my code, the less memory is used overall.

Either way, I’m going to try it to see if that helps.

I’ll grab a few more stats from my stripped down project and give you some more specific data points to work from.

Is this all your JS? What is calling BuildBusinesses?

Honestly I can’t really tell without looking at how everything is connected (basically the whole project) but if possible and if you have it on github could you link to it?

And yes I’d try to reduce the amount of observables, I don’t think your using Observables correctly just basd on how you had above every new object’s properties each being an Observable, I highly highly doubt you needed them as Observables. So you probably have more code doing things like so.

Also another thing change the following:

function BusinessBase() {
    this.BusinessID;
    this.SmallImageID;
    this.Business_Website;
    this.UserSelected;
    this.Business_PhoneNumber;
    this.Address1;
    this.Address2;
    this.City;
    this.State;
    this.ZipCode;
    this.Business_ShortDescription;
    this.Business_LongDescription;
    this.Latitude;
    this.Longitude;
    this.SortCategory;
    this.Trending;
    this.BusinessImageVC;

    this.Business_Name;
    this.Business_Category;
    this.Business_SubCategory;
    this.DistanceMiles;
    this.businessLogo = 'OfferLogo';

    this.set = function(obj) {
        for (var key in obj) {
            this[key] = obj[key];
        }

        if (obj.UserSelected) this.UserSelected.value = obj.UserSelected == 1 ? true : false;
        if (obj.Distance) this.DistanceMiles.value = Number(obj.Distance).toFixed(1);
    }
}

To:

function BusinessBase() {}

BusinessBase.prototype = {
    BusinessID: null,
    SmallImageID: null,
    Business_Website: null,
    UserSelected: null,
    Business_PhoneNumber: null,
    Address1: null,
    Address2: null,
    City: null,
    State: null,
    ZipCode: null,
    Business_ShortDescription: null,
    Business_LongDescription: null,
    Latitude: null,
    Longitude: null,
    SortCategory: null,
    Trending: null,
    BusinessImageVC: null,

    Business_Name: null,
    Business_Category: null,
    Business_SubCategory: null,
    DistanceMiles: null,
    businessLogo: 'OfferLogo',

    set: function(obj) {
        for (var key in obj) {
            this[key] = obj[key];
        }

        if (obj.UserSelected) this.UserSelected.value = obj.UserSelected == 1 ? true : false;
        if (obj.Distance) this.DistanceMiles.value = Number(obj.Distance).toFixed(1);
    }
}

You probably don’t even need the set function:

function BusinessBase(obj) {
    for (var key in obj) {
        this[key] = obj[key];
    }

    if (obj.UserSelected) this.UserSelected.value = obj.UserSelected == 1 ? true : false;
    if (obj.Distance) this.DistanceMiles.value = Number(obj.Distance).toFixed(1);
}

BusinessBase.prototype = {...what I showed above...}

Then instead of calling set like so:

function buildBusiness(rawObj) {
    var business = new BusinessBase();

    business.set(rawObj);

    return business;
}

You’d do:

function buildBusiness(rawObj) {
    var business = new BusinessBase(rawObj);
    return business;
}

Which you can just return instead of assigning to a variable:

function buildBusiness(rawObj) {
    return new BusinessBase(rawObj);
}

Also the set function did have an error though:

if (obj.UserSelected) this.UserSelected.value = obj.UserSelected == 1 ? true : false;
    if (obj.Distance) this.DistanceMiles.value = Number(obj.Distance).toFixed(1);

It shouldn’t have the .value property set It should just be:

if (obj.UserSelected) this.UserSelected = obj.UserSelected == 1 ? true : false;
    if (obj.Distance) this.DistanceMiles = Number(obj.Distance).toFixed(1);

mrcl3an: This all sounds very strange. I see nothing wrong with your code, but then again you’re not giving us a complete test case. An observable by itself shouldn’t take more than a few bytes of memory, there must be something else, completely mad going on here.

Thanks!

I’ll make some changes, test a bit more and see if I can get you the entire project. I’ll talk to you more on Slack about specifics.

I’ve done some more refactoring and I think each time I go through it I’m freeing up potential memory issues.

One question I have is this…

I have a list of Businesses per my code above, and when I select one of those businesses, I set a selectedBusiness object to what I’ve selected and then make a DB call to get more details. (I’m kind of recreating patterns from the Cairngorm framework in Flex)

I then have selectedBusiness bound to a view like so:

<Page ux:Class="BusinessDetailsPage" Background="#C8C7CC">
    <StackPanel Margin="0,0,0,40">

        <Select Data="{selectedBusiness}">

            <Text Value="{Business_Name}" FontSize="16" TextColor="#5D5E5E" TextWrapping="Wrap" />
            <Text Value="{Business_ShortDescription}" FontSize="12" TextColor="#979797" TextWrapping="Wrap" />

            <Grid Columns="auto,auto,1*" Visibility="{visibleForEvents}">
                <Text Value="{Business_Category}" FontSize="9" TextColor="#979797" TextWrapping="Wrap" />
                <Text Value=" - " FontSize="9" TextColor="#979797" />
                <Text Value="{Business_SubCategory}" FontSize="9" TextColor="#979797"     TextWrapping="Wrap" />
            </Grid>

        </Select>

    </StackPanel>
</Page>

I can’t get those properties to update without makeing them all Observables, which is why I was doing it that way. I’ve now broken this up into two different objects though. One that is not full of observalbes to fill my lists with. And then a single object for this selectedBusiness which has Observables.

Any advice here? Or a better way to achieve the same thing? My thought is to reuse this view and when I select a business it just updates the data to the newly selected business.

It should work but all depends on how you are exporting that Observable:

var obj = {
 Business_Name: 'some name',
 ...
}

var selectedBusiness = Observable(obj);

module.exports = {
 selectedBusiness: selectedBusiness
}

Now wherever you are making changes it should be as the following:

obj.Business_Name = 'new name';
selectedBusiness.value = obj;

OR:

selectedBusiness.value.Business_Name = 'new name';

Haven’t tested this code but should work

But actually you should be able to just without even using an Observable:

var selectedBusiness = {
 Business_Name: 'some name',
 ...
}

module.exports = {
 selectedBusiness: selectedBusiness
}

Again its really hard to follow what’s going on without seeing the whole project.

Ok, so you need to reassign the obj to the observable’s value… it won’t just update on it’s own unless .value changes? Let me see how I’m doing it. Maybe I was using Observables because I wasn’t reassigning the entire object.

RE: The entire project - it’s huge and I don’t mind sharing to specific people. I’ll have to zip it up and send it over to someone though, or share it privately on dropbox or something. It’s not on github, we’re using a different repo and I’m not in control of the privs.

No sorry, relook at my reply its updated, but those are different things you can try, again not sure, and if you are on the slack team, I’m @edwin there

Ok, I think I’m getting this now. I’ve got a simple test working, now I’ll try implementing in my larger app.

Thanks!

Ok, it took me a bit to get it to work in my entire project, but what I’ve discovered is that if at any point you assign selectedBusiness.value = "";, then it breaks the binding. It seems anything you set .value to afterwards will not update in the view. (althought the Observable updates properly)

I was using that as a way to “clear” my selectedBusiness, but I actually don’t think I need it. FYI.

Try selectedBusiness.clear()