By Ben Garney
 
Created
12 May 2011
 

Requirements

 
Prerequisite knowledge

Familiarity with ActionScript 3 and its object-oriented features as well as familiarity with Flash application development.
 

 
User level

Intermediate
 

 
Required products

A big part of what makes Apple iOS such a great mobile platform is its user interface. Whether you are developing natively or with Adobe Flash Professional CS5.5, users of iOS devices expect you—the application developer—to make your application fit in with the existing iOS experience. An essential part of that experience is interruptibility: your Adobe AIR application needs to save its state frequently and automatically so that it can be switched automatically as users expect—and as Apple specifies in its iPhone Human Interface Guidelines.
 
This article presents some strategies for saving your application state on iOS devices. The basic capabilities of iOS devices are covered, as well as application design techniques to help give a seamless user experience. I also provide code that demonstrates different techniques for saving state.
 

 
Considering state issues

The Apple iOS runs only a single user application at a time to keep the device responsive. Whenever the user launches another application, or an outside event like a phone call occurs, your application can be terminated. This can lead to frustrations for the user: imagine if you were writing a text message or e-mail and a phone call came in and you lost your work! Because of this, it is essential that your application save its state regularly and automatically.
 
My colleagues and I at PushButton Labs developed a game called Trading Stuff in Outer Space for the iPhone last year using the Flash Professional CS5 (see Figure 1). It is a simple space-based trading game that was developed in eight days to explore the technology. One of the goals of the project was to provide an experience indistinguishable from any other iPhone game. As a result, we had to figure out how to make saving state work.
 
Trading Stuff in Outer Space game screen
Figure 1. Trading Stuff in Outer Space game screen

 
Saving state on iOS devices

We envisioned the scenario of applications that lose data when they are interrupted. Of course, the built-in iOS apps don't do that—when you go back to your text messages after that interrupting phone call, you pick up right where you left off. This is done by the simple expedient of saving state when the application is ordered to quit by the OS. In the Objective-C world, you will have to implement this yourself (see the appropriate section of the iOS Application Programming Guide), and it is no different when you are developing with Flash.
 
 
Saving on exit
How do you know when to save in Flash? Since AIR applications for iOS share the same API as AIR apps for Adobe AIR, you can listen for the NativeApplication.EXITING event to know when to save your state:
 
package { import flash.desktop.NativeApplication; import flash.display.Sprite; import flash.events.Event; public class Save1 extends Sprite { public function Save1() { // Listen for exiting event. NativeApplication.nativeApplication.addEventListener(Event.EXITING, onExit); // Load data. load(); } private function onExit(e:Event):void { trace("Save here."); } private function load():void { trace("Load here."); } } }
 
Saving on important events
However, you should also save after crucial events or after a certain interval has passed. For Trading Stuff in Outer Space, crucial events include whenever the player trades (since it is an action that takes a lot of thought and has major effects on gameplay), when the player arrives at a planet, and whenever the player starts or ends a game. For an interval, we chose a period of 30–60 seconds, so that the user would never lose more than a minute's playtime. We chose that threshold because we felt that it was high enough to allow responsive play but low enough to avoid infuriating a user if something causes saving-on-exit to fail.
 
Unfortunately, the EXITING event isn't 100% reliable. It doesn't always get fired—sometimes due to time limits in the OS, sometimes due to other causes. Your application may crash (although it's unlikely), in which case the normal exiting behavior won't happen. So what we did was to save after every major operation the user performed, as well as approximately once a minute. That way even if users did manage to quit without the application saving state, it was unlikely they would lose more than few seconds of work:
 
package { import flash.desktop.NativeApplication; import flash.display.Sprite; import flash.events.Event; import flash.utils.setInterval; public class Save2 extends Sprite { public function Save2() { // Listen for exiting event. NativeApplication.nativeApplication.addEventListener(Event.EXITING, onExit); // Also save every 30 seconds. setInterval(save, 30*1000); // Load data. load(); } private function onExit(e:Event):void { save(); } private function save():void { trace("Save here."); } private function load():void { trace("Load here."); } } }
 
General applicability
The issues facing an AIR app on iOS devices are not that different from those facing SWF content in the browser. Users' browsers might crash or users might (accidentally or on purpose) navigate away from the page. Even desktop users may want their applications to always be stateful. You can use the same saving code to enhance the user experience on the web, devices, and the desktop.
 

 
Methods for saving

You can save your state in two main ways with AIR applications on iOS devices. One is with an LSO (local SharedObject). As you know, SharedObject.getLocal() (see the documentation) can be used to store data locally. This is convenient for a variety of reasons, not least of which is that you can use AMF to store object graphs:
 
private function save():void { // Get the shared object. var so:SharedObject = SharedObject.getLocal("myApp"); // Update the age variable. so.data['age'] = int(so.data['age']) + 1; // And flush our changes. so.flush(); // Also, indicate the value for debugging. trace("Saved generation " + so.data['age']); } private function load():void { // Get the shared object. var so:SharedObject = SharedObject.getLocal("myApp"); // And indicate the value for debugging. trace("Loaded generation " + so.data['age']); }
In the end, serializing objects directly worked against us in this game application. Depending on your application, using SharedObject might be a perfect fit. For Trading Stuff, I had a lot of complex interrelated data to store, and I also wanted to segregate game state into frequently changed and infrequently changed elements, so using LSOs wasn't a good fit. More on that next.
 
 
Saving using file objects
The other approach is to use File objects directly. You can write asynchronously in order to avoid disrupting the frame rate. If different parts of the game state change at different frequencies, you can store them in separate files so that only what actually changes is touched. You have to do your own serialization, but this isn't as bad as it sounds. (You can even use readObject() and writeObject() to store a whole object at once, although this can cause problems in some cases.)
 
public var age:int = 0; /** * Get a FileStream for reading or writing the save file. * @param write If true, we will write to the file. If false, we will read. * @param sync If true, we do synchronous writes. If false, asynchronous. * @return A FileStream instance we can read or write with. Don't forget to close it! */ private function getSaveStream(write:Boolean, sync:Boolean = true):FileStream { // The data file lives in the app storage directory, per iPhone guidelines. var f:File = File.applicationStorageDirectory.resolvePath("myApp.dat"); if(f.exists == false) return null; // Try creating and opening the stream. var fs:FileStream = new FileStream(); try { // If we are writing asynchronously, openAsync. if(write && !sync) fs.openAsync(f, FileMode.WRITE); else { // For synchronous write, or all reads, open synchronously. fs.open(f, write ? FileMode.WRITE : FileMode.READ); } } catch(e:Error) { // On error, simply return null. return null; } return fs; } private function load():void { // Get the stream and read from it. var fs:FileStream = getSaveStream(false); if(fs) { try { age = fs.readInt(); fs.close(); } catch(e:Error) { trace("Couldn't load due to error: " + e.toString()); } } trace("Loaded age = " + age); } private function save():void { // Update age. age++; // Get stream and write to it – asynchronously, to avoid hitching. var fs:FileStream = getSaveStream(true, false); fs.writeInt(age); fs.close(); trace("Saved age = " + age); }
Both of these should be straightforward to understand. If you've done Flash or AIR development, you've almost certainly dealt with either SharedObject or File. If not, you might want to refer to their documentation for details and examples.
 
You can also use readObject() and writeObject() to store or retrieve an object from a File object. This is powerful and saves you from a lot of repetitive serialization code. However, if you aren't careful, it can introduce problems: for instance, saving a DisplayObject won't work due to the various helper objects and complex relationships involved. Even saving pure data classes can be risky because they may have references to other objects that you don't want to serialize.
 
Saving the state of your whole application is, of course, more involved. I discuss this issue next.
 

 
State management architecture

The larger issue I ran into was serializing all my application data. Figuring out where to write is easy, but figuring out what to write is a little trickier. Naturally, because I am a programmer, I wanted to do it with as little work as possible.
 
The first thing I tried was directly serializing parts of my DisplayObject hierarchy and game state via AMF in a SharedObject. This was attractive initially because I figured I could throw references to a few key parts of my application into the SharedObject and be done. However, this didn't work: DisplayObjects have lots of helper objects with strange construction restrictions that are not AMF-friendly. It also led to uncontrolled serialization, where the object I was serializing contained a reference to other objects that ended up bringing in most of my application—and I didn't want to waste time debugging issues from strange dangling objects being serialized.
 
So, instead, I switched to use the File save() method. This meant I had to write some code to load and save objects that made up the state of my application. Up front, this involved more lines of code, but since they were direct and simple—and I did not plan on modifying the application much past launch—this approach ended up being much quicker to write and debug than the fewer lines of code that would be required for a more automatic solution.
 
The following code snippet shows what saving and loading looks like for the user's current waypoint:
 
// Serialize current waypoint. gfs.writeInt(currentWaypointIndex); gfs.writeBoolean(wayPoint != null); if(wayPoint != null) { gfs.writeFloat(wayPoint.x); gfs.writeFloat(wayPoint.y); } // Load waypoint state. currentWaypointIndex = gfs.readInt(); if(gfs.readBoolean()) { wayPoint = new Point(); wayPoint.x = gfs.readFloat(); wayPoint.y = gfs.readFloat(); } else { wayPoint = null }
As you can see, the serialization code has to allocate objects when appropriate; it has to detect if variables are null, and note that in the file; and it has to write each field with the right type. This can be a little overwhelming at first, but with a little practice you'll quickly realize that you are reusing a small set of common idioms for nearly every task.
 
I also made a singleton to manage all my game state. It kept track of the player's inventory, the locations of everything in the universe, the status of enemies, progress with missions, and so forth. From this singleton, I had methods to reset the game's state, to write it, and to read it back again. In some cases (such as inventory or missions) the state was owned by the singleton. In other cases (such as positions of game objects like the player and planets) the state is managed elsewhere, by other objects, but the singleton can get to it for saving purposes. On top of this foundation, it was easy to implement various saving strategies, as mentioned previously.
 

 
Where to go from here

Saving state is a key part of making your content fit smoothly into the iOS experience. Since only one application at a time can currently run on iOS devices, it's important to store not only the user's data, but the state of your application's UI. It's not a difficult detail, but it is an important one. I hope this article has helped you understand the key pieces involved in implementing this important functionality in your own application.
 
Saving state is a broad topic; there's no one-size-fits-all solution. A quick overview of the many different options for serialization can be found in the Serialization entry on Wikipedia. Flash natively supports XML (see Colin Moock's book, Essential ActionScript 3, for several great chapters on XML and E4X) and AMF, which are good building blocks for your serialization needs. There are also libraries like as3corelib that add support for JSON and other serialization formats. Of course, when you're saving application state between runs, what matters most is that the technology you choose is simple, easy to work with, and reliable.
 
From here, you might want to take the sample code included with this article and try to integrate it with your own content. There are a few decision points, such as using local SharedObjects or File objects, or saving frequently vs. infrequently. Make sure to run a few tests before deciding one way or the other; informed decisions are always best, and the information you need will depend on your specific application. Once you have a solution, test it by frequently quitting in the middle of your program's execution. It might not be possible to restore every piece of state exactly, but with surprisingly little work you will get close enough so that users accept your application as part of their positive iOS device experience.
 
Check out this series of articles to help you learn more about developing for iOS using Flash Professional: