Caching Computed Properties
David brings up another good point in a comment on my recent post about computed properties. If you have a property that is computed but only changes occasionally, you can use a hybrid approach with both observers and computed properties to cache this value efficiently. Here is how the fullName example we used yesterday would be implemented:
Addressbook.Contact = SC.Record.extend({
// firstName, lastName provided automatically by server
// computed property returns fullName. Use cached value if
// provided or compute. Save computed value in cache.
fullName: function() {
if (!this._fullName) {
this._fullName = this.getEach('firstName','lastName').compact().join(' ');
}
return this._fullName;
}.property(),
// clear fullName cache whenever firstName or lastName changes.
fullNameShouldChange: function() {
this.propertyWillChange('fullName') ;
this._fullName = null; // clear
this.propertyDidChange('fullName');
}.observes('firstName','lastName')
});
Note how the computed property here is not listed as having any dependent keys. Instead, we write both an observer and a computed property. The observer will notify that the fullName property has changed, but avoids the cost of calculating the new fullName property itself. This still only happens when another object tries to get() the value of the fullName property.
For an example this simple, the added overhead of managing this caching probably does not yield enough benefits, but if computing the fullName property is significantly more expensive, its easy to see how this might be useful. Let’s see, for example, how you might use this to automatically lazily load some data. The example below assumes you are writing a twitter client and lazily loads the twitter feed for the current user. Whenever the user changes, it clears the feed cache so that the next time you ask for the feed it can load:
Twitter.feedController = SC.Object.create({
// this is the name of the user you want to load
user: 'okito',
// computed property lazily loads the twitter feed for the
// current user.
feedContent: function() {
if (!this._feedContent) this.loadFeedContentFor(this.get('user'));
return this._feedContent;
}.property(),
// uses Ajax to load the new feed data, then calls
// feedContentDidLoad() with the new feed content.
loadFeedContentFor: function(user) {
// loading code goes here...
},
// observe the user and reset the feed when needed.
userDidChange: function() {
this.propertyWillChange('feedContent');
this._feedContent = null;
this.propertyDidChange('feedContent');
}.observes('user'),
// called when loadFeedContentFor() gets the data.
feedContentDidLoad: function(feedContent) {
this.propertyWillChange('feedContent');
this._feedContent = feedContent ;
this.propertyDidChange('feedContent') ;
}
});
Now an indecisive user can change their mind as often as they want about whose feed they want to view and your client won’t actually try to load the feed until you need to actually display the data. Best of all, the rest of your application never has to understand any of this logic. It just binds to the user and feedContent properties and trust the controller to manage it accordingly.
One More Thing… (Sorry, couldn’t resist)
You can use the technique described above to cache computed properties in your applications today. Over the last year, though, this has become such a common design pattern, that we’ve decided to support it natively in SproutCore. Starting with the RC1 release of SproutCore 1.0, you will be able to add cacheable() to your computed property and SproutCore will cache it for you. Here is how you would cache the fullName example above:
fullName: function() {
return this.getEach('firstName','lastName').compact().join(' ');
}.property('firstName', 'lastName').cacheable()
That’s it. No observers or private variables are required. SproutCore will automatically cache the return value of this property the first time you get() it. It will then use the cached property until one of your dependent keys (’firstName’ or ‘lastName’ in this case) changes or until you call propertyDidChange('fullName') on the record.
This new feature will not only help you eliminate glue code but also can make your application quite a bit faster. Don’t let that stop you from using cached computed properties today with the techniques describe above though. It can’t really help you clean up your code. Thanks for the tip, Dave.

Discussion Area - Leave a Comment