Sunday, May 2, 2010

Bindings & Observers don't deactivate on destroy

Currently in Sproutcore, bindings & remote observers (observers on other objects) are not deinitialized when the object is destroyed.

In the case of both bindings & remote observers, the remote object ends up with a reference to this object. Hence, this object does not get garbage collected & remains around till the remote object becomes eligible for GC.

If the remote object is a controller, chances are that it remains around for the lifetime of the application. Hence, this object also remains around in a destroyed state but still consuming memory, till the browser window is closed.

For a long running application, the browser accumulates a huge amount of memory over a period of time.

More importantly, since the bindings & observers are still active, they may interfere with bindings & observers on newer objects representing the same data. This can lead to unpredictable results.

This issue manifests only if your application is long running or keeps creating and destroying objects for the same data periodically.

Currently there is no decent solution for this, as Javascript has neither weak references nor lifecycle callbacks for an object. So its not possible to have references without preventing garbage collection & there is no guaranteed place to remove the hard references.

Views are the objects that are usually created and destroyed the most in any application & they have the most number of bindings to long-living objects like the controllers. So if we are able to solve this problem for views atleast, it reduces the problem greatly.

As it turns out, there is a workaround:

1. Lifecycle of views is completely controlled by sproutcore. So the destroy() method almost always gets called on views once they are removed.

2. All bindings created using the approach:
  myPropNameBinding: 'MyApp.Foo.x'
are maintained in an array called "bindings" within the object itself. We can iterate through the array in destroy() and disconnect the bindings & release the references.

Unfortunately, there is no straightforward approach to deinitialize observers. But local observers don't cause a remote reference and hence don't hold back the object from being garbage collected.

So, if we ensure that we always use local observers (observers on properties of the same object) only, then we can ensure that remote connections between views and other objects are cleaned up properly, just by disconnecting bindings.

So instead of writing something like

MyApp.Foo = SC.View.extend({
  myObesrvingMethod: function() {
    ...
  }.observes('MyApp.Bar.xyz')
});

you need to create a binding to the remote object & then create a local observer on it:

MyApp.Foo = SC.View.extend({
  myBarXyz: null,
  myBarXyzBinding: 'MyApp.Bar.xyz',

  myObesrvingMethod: function() {
    ...
  }.observes('myBarXyz')
});

Then, on all these views, the destroy() method can be overridden along the lines below:

  destroy: function() {
    this.bindings.forEach(function(binding) {
      binding.disconnect();
    });
    this.bindings = null;
    sc_super();
  }

Difference between local & remote queries

The difference between local & remote queries may look obvious from their names; local queries query on the local store vs. remote queries query remotely on the backend via the DataSource. But that is not the case.

Both Local & Remote queries have the following in common:

1. Contact the DataSource on the first find()
2. Don't contact to the DataSource on subsequent "find"s on the same query object
3. Contact the DataSource when refresh() is called on the RecordArray returned from a previous find() on the query

The 2 types of queries differ in - "How is the list of data in the results determined?"

Remote queries need the DataSource to tell it the exact list of storeKeys that represent result of the query. The DataSource usually does this by calling store.loadQueryResults(query, listOfStoreKeys).

Even though a new record has already been loaded into the store using store.loadRecord(), that record's storeKey needs to be explicitly included in the list passed to loadQueryResults(). Otherwise, it will not show up in the query results.

Local queries look at the store for all objects matching the given conditions and automatically include them into the query results.

It does not matter if a record was loaded into the store in response to this local query or due to some other remote query that got fired. If the record matches the conditions of the local query, it gets included into the results.

Understanding this difference is key to deciding when & how to use local vs. remote queries.

Since local queries also fetch data from the backend via the DataSource, we can then argue about the need for remote queries at all. There may be many cases where remote queries are useful e.g. the backend decides on the list of results for the query based on a hidden attribute visible only to the backend (hence it cannot be included as a condition on the local query). There may be many other cases out there for remote queries.

We can also classify the queries in our application strictly as local & remote. If the DataSource is designed to always ignore local queries, then we get an application where we always use remote queries to fetch data from the server & load them into the store, while we always use local queries to get records matching particular conditions, out of the store. Personally, I like this approach very much as it imposes a certain order in the application.

This explanation of the difference between local and remote queries directly leads to another understanding - the need of conditions in local vs. remote queries.

Since local queries automatically try to evaluate if a record in the store matches the conditions of the query, conditions given on local queries need to be in a format that sproutcore can understand.

On the other hand, remote queries just use the list of storeKeys given by the DataSource to find the records that are part of its result. Hence the conditions given on a remote query do not matter. Ususally, remote queries are defined as constant properties in your application object & the DataSource just compares the incoming query object against one of these remote query objects and decides what needs to be fetched from the backend.

Tuesday, March 30, 2010

Modify argument value before calling sc_super()

I have overridden a function "testFn" in a subclass which does something extra and then calls the parent class function. But I want to change one of the argument values before calling the parent class function. The standard javascript approach would be something like this:


testFn: function(arg1, arg2) {
  // ... do something
  var myarg2 = "new arg value";
  // use the following instead of sc_super() to that you can pass custom arguments
  arguments.callee.base.call(this, arg1, myarg2);
}


But javascript also supports some special handling of the "arguments" object, that gives us a cleaner implementation as below:


testFn: function(arg1, arg2) {
  // ... do something
  arg2 = "new arg value";

  sc_super();
}


To understand what's going on, you first need to understand that when doing "sc-build", all occurrences of the "sc_super()" is replaced with the following line (verbatim):


arguments.callee.base.apply(this, arguments);


So, the "arguments" object (provided by javascript) is passed to the parent function (stored by sproutcore in arguments.callee.base) asis.

In our second code snippet, although we just reassign the value of "arg2", the same value change automatically reflects in the "arguments" object (as if arg2 is a reference variable to the corresponding value in the "arguments" object). So any functions called from here one to which we pass the "arguments" object will have the new value of arg2.

But, the "arguments" object in the function which originally called "testFn" does not get this changed value. So, this also means that when you call a function, a copy of the "arguments" is passed instead of the original "arguments" object.

To try this out in any browser, paste the following snippet in the javascript console of any browser:


fnsub = function(x,y) {
  console.log("sub says: " + x + ", " + y);
};

fn = function(x, y) {
  console.log(arguments[0] + ", " + arguments[1]);
  y = "universe";
  fnsub.apply(this, arguments);
  console.log(arguments[0] + ", " + arguments[1]);
};

fn("hi", "world");


You will see an output similar to:


hi, world
sub says: hi, universe
hi, universe

So, although you changed the value of "y", it internally changed the value of "arguments[1]"

Thursday, February 25, 2010

Disable packing of sproutcore javascripts

If you need to debug sproutcore javascripts & want them to load as individual files instead of a single packed "javascript.js" file, add the following to your Buildfile:


mode :debug do
  %w[sproutcore sproutcore/runtime sproutcore/datastore sproutcore/foundation sproutcore/desktop].each do |target|
    config target, :combine_javascript => false
  end
end


This will load the javascripts unpacked when running sc-server (which defaults to "debug" mode), but will get packed when using sc-build (which defaults to "production" mode)

Friday, October 16, 2009

SC Build Tools

Controllers

SproutCore comes with build tools to help you generate object from templates, one such example is to create a object controller

SC-gen  <ProjectName>.<controllerJSName>Controller

for ex:  "SC-gen HelloWorld.appController"

creates a "HelloWorld.appController" object in the file "app.js" inside the "controllers" folder of the application.