SproutCore 1.0: New Bindings and Observers
Property observers and bindings form the very core of SproutCore’s API. In fact, SproutCore is one of the few frameworks to have implemented bindings and observers from the very beginning instead of bolting it on at a later date. This contributes heavily to SproutCore’s compact design.
The centrality of bindings and observers also makes it one of our most important so that is why it was one of the first things we revisited for SproutCore 1.0. Since this code as so old, it was quite easy to rewrite it using some more efficient techniques, giving us a 2x performance boost when creating new bindings and setting up new observers.
In addition to a raw perf boost, we also changed some key behaviors related to timing that could change how your application behaves. These changes should speed up your app even further by reducing the number of bindings that fire in your application. Depending on how you’ve written your code, however, it could change how your code behaves. Here is what we did:
Change 1: setIfChanged Becomes Default
Today, every time you call set() on an object, SproutCore will invoke any observers you have as well, even if the value you are setting has not changed. For example, if you write some code like this:
obj = SC.Object.create({
firstName: null,
firstNameObserver: function() {
console.log("observer fired");
}.observes('firstName')
});
obj.set('firstName', 'John');
> observer fired
obj.set('firstName', 'John');
> observer fired
obj.set('firstName', 'John');
> observer fired
Now, the default behavior has changed. With the new code, observers will only fire if the value you set actually changes. For example, the output of the above code with SproutCore 1.0 is:
obj.set('firstName', 'John');
> observer fired
obj.set('firstName', 'John');
obj.set('firstName', 'John');
This means you can write less code to make sure your observer is not fired more often than necessary. Big win.
The only problem with this change is that this means computed properties may sometimes not be set more than once if you call set() with the same value. Usually this will not matter, but if your computed property needs to be called everytime you try to set the value, you can change this behavior by adding the indempotent() helper to your property:
fullName: function(key, value) {
// do something interesting here
}.property().indempotent(NO)
Adding this qualifier to your property will cause it to be called every time there is a set() instead of just when the value changes.
Change 2: Binding and Observer Timings
Currently, whenever a property changes on your application, observers are notified immediately unless you are already inside another observer handler. Then they are queued and notified when the root observer exits. Since bindings are implemented using observers, bindings will trigger when your other observers finish but before the set() returns, again unless you are more than one level deep in observers.
If this sounds confusing to you, that’s because it is confusing. It also makes it hard to predict exactly how change notifications will propagate through your application.
With SproutCore 1.0, this timing behavior is significantly cleaned up. The rules are simple:
- Observers (i.e. methods you setup with
observes()oraddObserver()/removeObsevers()) will fire immediately when you callset(), unless wrapped inside of abeginPropertyChanges()/endPropertyChanges()pair. In that case, they will fire immediately when you callendPropertyChanges(). - Bindings on the other hand queue up. They fire only once at the end of the run loop, when all of your normal event handling or timer code finishes executing. Bindings are also coalesced. If a binding changes more than once during a single run loop, it will still only fire once at the end.
The net effect of this timing change will be that when a property changes on an object, the observers you have written for that object will execute immediately so you can update other state within your object. But, the bindings that tie together the objects in your app will fire all at once. This will cause changes to relay through your app rather like a wave, starting at the model or view layer and propagating to the other side in layers.
This more controlled rate of change will generally not require you to change any code, but it does mean that code you put in to gate changes and control duplicate messages will be less necessary. It will also generally speed up your application since binding change notifications occur less frequently.
Change 3: Cache Computed Properties
I mentioned this the other day so I won’t spend a lot of time on it now, but the basic idea here is that often the return value of a computed property only changes when one of its dependent keys or some other state changes. In the mean time, multiple calls to get that property could return a cached value. Caching used to require many lines of code, but now it can be done with just one. Here is how you write a cached property:
contact = SC.Object.create({
firstName: 'John',
lastName: 'Doe',
// computed property, cached until first or last name changes
fullName: function() {
console.log('computing fullName') ;
return this.getEach('firstName','lastName').compact().join(' ');
}.property('firstName', 'lastName').cacheable()
});
contact.get('fullName')
> computing fullName
> "John Doe"
contact.get('fullName')
> "John Doe"
contact.set('firstName', 'Jane');
contact.get('fullName');
> computing fullName
> "Jane Doe"
contact.get('fullName');
> "Jane Doe"
Now the fullName computed property will be called only once and cached. You can get() as often as you like without paying the cost to recompute the value. If you ever need to recache a computed property without changing one of its dependent keys, you can do so by calling properyDidChange().
Change 4: Use a target and method with addObserver() and removeObserver()
The low-level addObserver() and removeObserver() methods are used to setup and remove observers on an object. Their API currently requires you to pass the property you want to observe along with a function to execute. To execute the function in the context of a particular object, you had to wrap you function using Function.bind(), which adds a stack frame, another object and closure and generally slows everything down.
Now addObserver() and removeObserver() will be able to accept a target and method, which will be tracked separately for you. This allows you (and SproutCore) to avoid having to create these extra closures, which increases performance and helps on limited JavaScript engines like IE7’s JScript.
Change 5: Disable Automatic Observer Notification
Normally, when you call set() with a new value on a property, SproutCore will automatically notify observers when the property has changed. Sometimes, however, you may have a property with a more complex behavior and you don’t want the automatic notification process to take place. Now you can override this behavior by implementing a new method on your class called automaticallyNotifiesObserversFor(key). Returning NO for this method will disable automatic notification for the passed key.
This technique is most useful when used with computed properties. For example, let’s say you decide to implement a computed property that only allows values greater than 10 to be set on it. Here is how you might implement this:
obj = SC.Object.create({
// computed property can be set to any number
// as long as it is greater than 10
value: function(key, value) {
if ((value !== undefined) && (value > 10) && (value !== this._value)) {
this.propertyWillChange('value'); // implement notification manually
this._value = value ;
this.propertyDidChange('value', value) ;
}
return this._value ;
},
// disable automatic notification for this property
automaticallyNotifiesObserversForKey: function(key) {
return (key === "value") ? NO : YES ;
},
// add observer just to show what happens
valueDidChange: function() {
console.log("value did change to: %@".fmt(this.get('value')) ;
}.observes('value')
});
obj.set('value', 20) ;
> value did change to 20
obj.set('value', 5);
[no output]
obj.set('value', 21)
> value did change to 21
obj.set('value', 21)
[no output]
This is definitely an advanced topic, but if you need to control precisely how and when property change notifications happen, it can be a god-send.
In general, you will be able to use the new property observers and bindings in SproutCore without having to worry about the changes here. You should just notice your app running a little faster. Understanding how some of these work though can dramatically reduce the amount of code you have to write in certain advanced situations. If you have questions about this once its available, be sure to ask on IRC or the mailing list!

Interesting. I have much of the same thing in my own JavaScript framework. Except I’ve done it by boxing all the types, and attaching my on change events to the boxed types.
So I have a boxed String, a boxed Integer, a boxed Array and so on. I have no special constructs for properties (setter and getters), I just use the boxed types, they behave almost as the build in types, except setting and getting their value is done using setValue() and getValue().
The object that has provided the most benefit in boxing, when building my applications has been the Array.
You can do some clever and vere reusable things in the UI by building on Boxed Arrays.
I’ve written an article about some of the stuff I’ve done at
http://obsurvey.com/UIA.aspx
Allan, is your code available anywhere? SproutCore supports something similar to your boxedarray concept with the SC.Array mixin. I have a post in the works on this…
No my code is not available, I’ve thought about doing that, but so far I just use it for my own projects. It’s still ripening