Just wanted to chime in and say that we did something similar for a project we're working on. It stores the current state in an NSMutableDictionary so we could retrieve values through a Firebase wrapper class during things like UITableView reloads when it’s not possible to defer to a later time because the data is needed immediately. We aren’t storing anything between launches, but it would be straightforward to write the state to a JSON file or plist.
In hindsight, conceptually this is good but I don’t know that it's ideal. Firebase really needs a way to query the current state if data is present, and return something kind of like a nonblocking socket’s EAGAIN error if it’s not. That was the motivation behind writing our caching layer, anyway. Unfortunately one of the problems is that we never know when data is no longer needed. That’s probably one of the reasons Firebase listers work the way they do. They tell Firebase what data needs to be retained and what doesn’t. We probably need to keep track of listeners and retire any data in the cache that's no longer being watched.
If I had it to do over again, I might consider using an object-relational mapping (ORM):
http://en.wikipedia.org/wiki/Object-relational_mapping
I used one in Laravel for a different php project with a MySQL backend and it worked great. We basically need a base class or protocol that can be added to a class and tell it which members map to which Firebase paths. Something like a FirebaseModel, where any properties in the model would map automagically and then their getters and setters could be overwritten. Here is a hypothetical example written in objectivejavascriptish with no error checking etc:
interface Person : FirebaseModel
{
Firebase firebase('persons');
String id; // maps to /persons/$id
String nameFirst; // maps to /persons/$id/nameFirst
String nameLast; // maps to /persons/$id/nameLast
String name; // virtual (overridden getter and setter)
Person()
{
this.primary('id');
}
}
implementation Person
{
String name() // override name’s getter
{
return this.nameFirst + ' ' + this.nameLast;
}
setName(String name) // override name’s setter
{
Array parts = name.split(' ');
this.nameFirst = parts[0];
this.nameLast = parts[1];
}
}
// Usage:
FirebaseModelCollection persons = Person.all();
TableView tableView;
persons.on('child_changed', function(Person person) {
tableView.reloadRow(
person.id);
});
TableView::reloadCallback(RowView rowView)
{
rowView.text = persons[rowView.id].name;
}
TableView::changedCallback(RowView rowView)
{
persons[rowView.id].name = row.text;
}
Assuming that the models only update synchronously with the main loop (meaning a model could never change during something like a table redraw), then a view controller could set up its view, load all of its models into a collection, and then get/set values from the collection without having to worry about any asynchronous code. Then if something in the model changed, it would be notified by the standard listeners but be passed a model rather than a node value.
I’m sure I overlooked something here, and am maybe skipping over some of the more interesting relationships that can be set up between models with an ORM, but this could be a useful replacement for manually caching things.
Maybe the FirebaseModel could tell Firebase which data to store for offline access. Or more accurately, I guess any listeners activated by the FirebaseModel during a session would alert Firebase that those nodes need to be kept till next time. So if the app didn’t listen on a node during a session, that node in the cache would get freed at the next launch (or more accurately, marked in the cache’s least-recently-used strategy that it was ok to free the node on the next cleanup). I guess the models wouldn’t be necessary for Firebase’s offline storage, they would just help the user visualize what data is being cached.
Zack Morris