Research Group for Applied Software Engineering
Forschungsgruppe für Angewandte Softwaretechnik

iOS Game State Tutorial

by Peter Pult

1. Introduction – goals of session

In this tutorial you’ll learn the basics in saving data to your local memory and syncing this data across multiple devices.

But why do we need to save data?
In software development we have the problem that all the data and objects created during the life cycle of your application get deleted when your application shuts down.
In the mobile app context there are a lot of distractions, which can lead to closing of apps, such as incoming phone calls, notifications popping up or environmental events happening around you. On multitasking devices the app than later should come back to where the user left off, but in the meantime the user could switch off the device or the app could be closed by the system due to memory shortness. So it is very important to persist this data, so it is stored for a later use.

Basically you have two ways to achieve local persistence in your app. You can either save data into a document or into a database. And since cloud computing became very popular you also have the option to save your data at another place in the world and get it back from there, when you need it.

In this tutorial I will give you a quick overview about four different options you have in iOS development to persist data locally and in which cases you should choose which option.
Also I will go into more detail on how to specifically save and read simple data from your local memory with the help of Property Lists commonly known as plists.
And I will talk about the basic concepts of iCloud, to show you one option how you can persist data via the cloud.

So at the end of this tutorial you should be able to:

  • Save simple data to a local plist
  • Read data from a local plist
  • And finally sync this data to other devices via iCloud
   
Figure 1 Propery list examples Figure 2 The cloud from Apple iCloud

2. Motivation

Saving individual user data is used in (nearly) every app. Only apps like the calculator probably don’t save individual data of a user. There are a lot of different kinds of data you can save. This can be settings the user made in your app, the app state when your app is interrupted by an incoming call, output from a web API, whole documents an user creates like in the Paper app, favorites, locations, dates, language, nicknames and of course your game state when the game is ended or moved to background.

     
Figure 3 Stocks app – save your favorite stocks Figure 4 Cut the Rope app - save settings like language Figure 5 Doodle Jump app - save meaningful statistics

So as you see saving and loading data is everywhere and it is very important to understand the basic concepts and the differences between the different data types.

3. Implementation options in iOS

Of course there are multiple options to store data locally in iOS. I will just go briefly over four of the most common ones.

3.1 Property List (plist)

A property list is an document file format where you can archive NSObjects like a string, number or array into a XML structure, which can than be unarchived back into NSObjects when needed. Note that always all of the data is being loaded, so in doesn’t matter if you only want to get one value or all the values from the plist. This can be very memory intensive when handling large data sets.

Use a plist if you... Do not use a plist if you...
  • have a single, non-relational, one dimensional collection of data
  • are storing preferences, scores, save states, basic information from the web or other simple data
  • want to use a fully Objective-C API for reading and writing data
  • have relational or large data
  • don’t want to manage the schema of your data and if you don’t want to handle corruptions yourself

3.2 NSUserDefaults

NSUserDefaults is a class built right into the iOS SDK and should only be used to store little variables for user defaults or user preferences. It is very lightweight and easy to use. Don’t use it to store large data.[MORE 101 NSUSERDEFAULTS]

3.3 SQLite

SQLite will allow you to use a full SQLite database in your application. The default framework is in C so you probably want to use a wrapper to keep the Objective C syntax. Note that you will have to provide a seed database at the beginning.

Use a SQLite database if you... Do not use a SQLite database if you...
  • want to use a very light and quick database
  • have larger data sets with many collections of data
  • have relational collections with a foreign key
  • know the SQL query language
  • don’t want to work with a C framework (use a Objective C wrapper)
  • don’t want to escape SQL values

3.4 Core Data

Core Data will give you a fully functional database with lot’s of tools build right into Xcode. It is easy to implement and very scalable. Core Data was build and optimized for iOS and comes directly from Apple.

Use Core Data if you... Do not use Core Data if you...
  • want to use a very light and quick database
  • want to use tools directly build into Xcode
  • want a full Objective C wrapper
  • only have one collection of data with a small amount of data sets

4. Sample application

As a sample application I possibly just could open any app on my phone but in this tutorial I will use a sample app named “Fat.Finger”. Goal is to strike a round area. The difficulty is the round area get’s smaller every time you strike it.
The app only saves a few data: current level, games played in total, loses and wins.
This data is saved in a very simple plist and synced between devices with iCloud.

     
Figure 6 Fat.Finger app - first level Figure 7 Fat.Finger app - success screen Figure 8 Fat.Finger app - second level

5. Property List (plist)

Plists are deeply integrated into Cocoa and Core Foundation and store efficiently standard types of data. In this tutorial I won’t go into further detail about the API given by Core Foundation.

5.1 Overview plist

A plist can be represented as either XML or binary data. XML is very human readable but less performing than binary data. So as long as you don’t save a lot of data it is better to use XML because it is much easier to handle. So for simplicity all my examples build on plists represented by XML.
A plist starts of with standard header information, followed by the <plist> document type tag. This tag contains one root object, which is generally an array or a dictionary.

 
Figure 9 Example of plist represented as source code

The root object as well as all objects in all containers (arrays and dictionaries) must be of primitive types. In Cocoa they are called property-list objects. Figure 10 shows you a list of all property-list objects and their various representations.
At runtime the plist is represented based on the containing abstract types.

 
Figure 10 Property-list objects and their various representations

5.2 Load a NSDictionary from a plist

Now you could create the plist directly in code, but to get a better understanding of how plists are structured and how they are integrated into Xcode I’ll show you how to create a plist in Xcode.
So let’s start by creating a plist. Go to File -> New -> File... and select Resource -> Property List. Now enter a name (I’ll choose “gameStats” for this tutorial) and hit Create. Navigate to your new plist and open it. Here you can see the root object, which is an dictionary on default. Keep it that way and now add the data you wish to save enter values, types and keys as you wish. Just keep in mind that only primitive types are allowed.

   
Figure 11 Create a new... Figure 12 ...plist in Xcode

Now probably the first thing that comes to mind is: How can we use that data in our app workflow?
Reading a plist from disk is pretty straightforward both NSArray and NSDictionary have convenience methods to get and write to a plist. In this tutorial I will show you the convenience methods for a dictionary.

I will use a controller to handle the game statistics. This controller is of type NSObject and named “GameStatsController”. This class will have two major methods. The readGameStatsFromPlist method in which we will read the newest data from our plist and the saveGameStatsToPlist: method, which you’ll use later to save new data to our plist.

So in the readGameStatsFromPlist method we write:

Listing 1: readGameStatsFromPlist method of GameStatsController

- (NSDictionary *)readGameStatsFromPlist;

{       

    // Code snippet: Read dictionary from a plist

   

    // Get path to user's Documents directory

    NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

    NSString *plistPath = [rootPath stringByAppendingPathComponent:@"gameStats.plist"];

   

    // On first launch get plist from main bundle

    if (![[NSFileManagerdefaultManager] fileExistsAtPath:plistPath]) {

        plistPath = [[NSBundlemainBundle] pathForResource:@"gameStats"ofType:@"plist"];

    }

 

 

    // Read dictionary from path

    NSDictionary *dict = [NSDictionarydictionaryWithContentsOfFile:plistPath];

   

    // Check if reading from path was successful

    if (dict == nil) {

        NSLog(@"Could not read plist at path %@", plistPath);

        returnnil;

    }

   

    // Code snippet END

   

    NSLog(@"Read game stats from plist: %@", dict);

   

    return dict;

} 

The first thing we do is getting the path to the plist file. We start by getting the user’s directory and than add the filename to that path. Because we created the plist with Xcode it is saved in the main bundle until we save it on our own to the users directory. So on first launch we’ll have to get the plist from the main bundle.
Than we use the convenience method dictionaryWithContentsOfFile: to load the plist into a NSDictionary. If this was successful we return the dictionary, which will contain all the keys and values from the plist.

5.3 Save a NSDictionary to a plist

Okay after we have successfully loaded data from a plist the next question is: How do we save data to a plist?
Therefore as said before I will use in this tutorial the saveGameStateToPlist: method (Listing 2).

Listing 2: saveGameStatsToPlist: method of GameStatsController

Normal 0 21 false false false DE JA X-NONE /* Style Definitions */ table.MsoNormalTable {mso-style-name:"Normale Tabelle"; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:12.0pt; font-family:Cambria; mso-ascii-font-family:Cambria; mso-ascii-theme-font:minor-latin; mso-hansi-font-family:Cambria; mso-hansi-theme-font:minor-latin;}

- (BOOL)saveGameStatsToPlist:(NSDictionary *)dict;

{       

    // Code snippet: Saving a dicitonary to a plist

   

    // Get path to user's Documents directory

    NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];

    NSString *plistPath = [rootPath stringByAppendingPathComponent:@"gameStats.plist"];

   

    // Write dictionary as plist to path

    BOOL success = [dict writeToFile:plistPath atomically:YES];

   

    // Check if saving to path was successful

    if(success) {

        NSLog(@"Saved game stats to plist: %@", dict);

        [[GameStatsControllerdefaultController] updatePropertiesWithDict:dict];

        returnYES;

    } else {

        NSLog(@"Could not save to plist at path %@", plistPath);

        returnNO;

    }

 

    // Code snippet END   

}

This method works pretty much the same as the readGameStatsFromPlist: method, but this time we use the convenience method writeToFile:atomically: to save a dictionary as a plist. Important to notice is, that we write to the users directory and not to the main bundle, from where we first got our plist. The main bundle is not writeable because it is codesigned when the application is distributed to the App Store. (Note: Writing to the main bundle will work on the iOS Simulator, so always test you apps on a real device!)
As you see the saveGameStatsToPlist: method returns YES when writing was successful and NO if an error occurred. This way it is really easy to check if saving the new game statistics worked or not.

6. Sync with iCloud

So the next thing you might want to do is sync your data between all your devices. But how can we sync data easily?
For this purpose I’ll introduce to you iCloud.

6.1 Overview iCloud

“iCloud is a free service that lets users access their personal content on all their devices—wirelessly and automatically via Apple ID.”

iCloud was launched in 2011 and is popular since. The reason a lot of developers use it, is the deep integration of iCloud into iOS and MacOS. A lot of code runs along side even when your app is not running itself. This enables you to sync to all of your devices at any time and any state your device and your app is in.
Because iCloud is so deeply integrated it has more than 300M users . So there are more than 300M accounts you can count on, when you want to sync your app between different devices.
Note that this service is only available for apps from the Apple App Store or Mac App Store.
To test iCloud in development you need to set up a provisioning profile and entitlements, which we’ll discuss in more detail in just a second.

6.1.1 iCloud APIs

iCloud provides two different API’s to store data.
First is the Key Value Storage (KVS) API it is for discrete data (data that only contains distinct values) such as preferences, settings and simple app state. In this tutorial I’ll use KVS to store the game state in the cloud and sync it between my devices.

Use KVS for iCloud if you... Do not use KVS for iCloud if you...
  • want a very easy to implement solution
  • want to give your users the same experience on all your devices
  • want to sync simple and non-relational data
  • want to sync preferences, scores, settings or simple app state
  • have relational or large data

Second is the Document Storage API it is for user-visible file-based information such as documents in the Pages App, drawings in the Paper App or complex app state and can also be used to sync Core Data Storage.

Use Document Storage if you... Do not use Document Storage if you...
  • want to sync large or relational data sets with many collections of data
  • want to sync files and documents
  • want to sync a Core Data database
  • have very memory intense data which will probably exceed the space available in the user’s iCloud account (free storage is 5GB)

But the use of one API doesn’t have to exclude the use of the other API. Rather lots of apps use KVS and Document Storage parallel, depending on the data they want to sync.

One more note on iCloud APIs:
Never save passwords with one of the iCloud storage APIs. Therefore use the Keychain Service API.

I added some resources at the end to read more about iCloud document storage.[MORE ICLOUD STORAGE 102]

6.2 Prerequisites – Apple Developer and entitlements

To use iCloud you need to have an Apple Developer Account. If you don’t have one you will have to purchase one, as it is a prerequisite for using iCloud.
With that said you can start by creating a provisioning profile and connect a entitlement with it. An entitlement requests capabilities for your app – in our case the capability to use iCloud. Luckily since Xcode 5 this process is very easy.

 
Figure 13 The new capabilities tab in Xcode 5

Just create a new project or open an existing one and navigate to your project editor. There open the capabilities tab and just turn on iCloud. If you want to use KVS make sure you checked the Key-Value Store checkbox.
If you are not yet using Xcode 5 I provided some links at the end of this tutorial on this topic.[MORE ICLOUD ENTITLEMENTS 103]
All right you set up everything you need for iCloud. Let’s dive into code!

6.3 The NSUbiquitousKeyValueStore object and notifier

The data you want to save to iCloud will be placed in a local representation on your device. This special file system is known as ubiquity containers and is outside of your app’s sandbox. To get access to ubiquity containers you need the entitlements I mentioned before. For key-value storage this ubiquity container is named Key-Value Store.
At this point I want to remind you that I’ll only be talking about key-value storage in this tutorial.

In your app a special class handles all the requests between your app and the local key-value store: The NSUbiquitousKeyValueStore class provides a lot of methods for writing and reading values from the local key-value store. To get an overview of all methods and properties take a look at the NSUbiquitousKeyValueStore Class Reference.[MORE KVS REFERENCE 104]

 
Figure 14 Process of accessing the iCloud key-value store

Data is written atomically to the key-value store so either all of the data is written or none of it is. You can use this behavior to ensure that all of your data is saved together. Just put your mutually-dependent data within a dictionary.
Another important part of the syncing process is the NSUbiquitousKeyValueStoreDidChangeExternallyNotification it notifies your app everytime the key-value store gets an update. I will show you in a minute how you integrate it in your app.
All right so you set up the capabilities for your app and learned about the basic structures in KVS and iCloud, than you are now ready to do some coding.

6.4 Get new data from iCloud

In this tutorial I will read new data when the app launches and write it back when the app goes to background. Apple recommends this approach because it only uses capacity when needed.
So you start in your app delegate class in the application:didFinishLaunchingWithOptions: and start by checking for iCloud access. For KVS this isn’t really necessary, because if no account is attached to the device changes will still be saved and pushed as soon as the device is attached to an account. But for testing of course it is important to know if you have iCloud access at the moment.

Listing 3: application:didFinishLaunchingWithOptions: method of AppDelegate

id token = [[NSFileManager defaultManager] ubiquityIdentityToken];

if (token) {

    NSLog(@"iCloud access");

} else {

    NSLog(@"No iCloud access.");

}

 

// Get the shared local iCloud key-value store object, it knows which data to get and set because it is tied to the unique identifier set in the apps entitlements.

NSUbiquitousKeyValueStore *kvStore = [NSUbiquitousKeyValueStore defaultStore];

   

// Add a observer to the app, to get notified when changes are made to the local key-value store.

// NOTE: Not the app is connected to iCloud but the local key-value store, which works as an agent between iCloud and the app.

[[NSNotificationCenter defaultCenter]

        addObserver: self

        selector: @selector(storeDidChange:)

        name: NSUbiquitousKeyValueStoreDidChangeExternallyNotification

        object: kvStore];

   

// Get changes made while this instance of the app wasn't running.

[kvStore synchronize];

Then we add an observer for the previously mentioned NSUbiquitousKeyValueStoreDidChangeExternallyNotification to get notified when something changes in our local key-value store. The observer always calls the storeDidChange: method, which will handle the new data and decides what to do with it. After that we get the newest data from iCloud by calling the synchronize method. This is pretty much it for getting new values from the cloud. The only thing left to do is for you to decide whether to save the new values or better use your local ones. What I did at this point, I’ll show you later when I talk about key-value conflicts.

6.5 Save new data to iCloud

Saving data to iCloud is even easier than getting data. As stated it is best to save new data to iCloud when your app goes to background. So the next few lines we’ll work in the applicationDidEnterBackground: method.

So to set new values we just save the most current dictionary of our game stats for a key of our choice. I went with @”GAME_STATS” as my key identifier.
Note that we don’t have to call synchronize or anything else because the key-value store will detect the changes made by us and will push these changes automatically to the cloud and from there to all devices connected with the same Apple ID.
Normally after a few seconds the changes will be available on your other devices.

Well done, you can now sync data between all your devices wirelessly and automatically!

A little side note on the iCloud quota:
Your key-value storage has some capacity boundaries. In total you can save up to 1MB per application and a maximum of 1024 keys. So you can save one key with a storage volume of 1MB or you can save 1024 keys with each 1KB of storage volume or of course every combination in between.
You get notified if the user exceeds the quota with the value: NSUbiquitousKeyValueStoreQuotaViolationChange

6.6 Key-value conflicts

Syncing used to be very complicated and with iCloud Apples gives us a really convenient way to integrate syncing in every app. But still there is a problem you’ll have to handle yourself and these are key-value conflicts. Imagine a user who installs your game on his iPhone and starts completing some levels and syncs let’s say level 5 to the cloud. Later that day he decides to play your game on his iPad but because the iPad is in airplane mode it’s not getting the newest data and the user saves level 2. Later the device goes online and syncs the new data to the cloud. iCloud will detect a newer value and will override the old one. Now when your user opens your game on his iPhone again the app will get level 2 from the cloud and syncs it to the app.
At this point you as a developer have to interrupt and compare your local level with the one from the cloud to decide which one to keep.

I used my local plist do compare values from the cloud with the local ones and decided depending on the most games played in total which values to use. So when the cloud provides more games played I override the local plist with the new data from the cloud. That is as easy as it can get. See my example in Listing 5.

6.7 Debug iCloud

With every implementation comes code to debug. So here are some suggestions for debugging your iCloud app.
First always – always – test your code on a real device. In case of iCloud test it on multiple devices. Although since Xcode 5 and the new iOS simulator you can use and test iCloud never only rely on the results you get from there.
Monitor your network traffic if it get’s to high or repeats at the same action get a closer look at what is happening there. For that use the new build in debugging pane in Xcode 5.

 
Figure 15 The new debugging pane in Xcode 5

Use the airplane mode to induce conflicts in your app.
Take a look at developer.icloud.com to see your documents (if you are using the iCloud Documents API) saved from your account or just to see if your ubiquity containers were synced to iCloud.

6.8 Best practices iCloud

And here an incomplete list of best practices for key-value storage in iCloud:

  1. Always test on real devices.
  2. Keep a local representation of your cloud data to resolve key-value conflicts.
  3. Use it for app state and simple configurations of your app.
  4. Watch quota violations to eventually save your data in documents instead of key-values.

7. Summary – what you should have learned

After this tutorial you should be able to identify a property list (plist) from it’s structure and understand how to read data from a plist and write updated data back to the users document directory.
Also you should know the difference in writing permissions between the main bundle of your application and the documents directory.

Furthermore you now understand the basic concept behind iCloud and how it uses ubiquity containers to sync data to every device connected with the same iCloud account.
Because you know how to use the NSUbiquitousKeyValueStore class you can now get new data from iCloud and later sync updated data back to the cloud. Also you know what a key-value conflict is and how to resolve it.

 
Figure 16 Propery list example
 
Figure 17 The cloud from Apple iCloud

8. Where to go from here

Of course saving data is a huge topic and it doesn’t end with key-value storage in iCloud. It is quiet possible that your application exceeds the use of KVS and is looking for something bigger. Than take a closer look at the documents storage API of iCloud and NSData.
For even more advanced data complex structures Cocoas offers another framework for you named Core Data or for a close database option take a look at SQLite library which is integrated into iOS.

  • iCloud Documents Storage API
    • https://developer.apple.com/library/mac/documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesigningForDocumentsIniCloud.html
    • http://www.raywenderlich.com/6015/beginning-icloud-in-ios-5-tutorial-part-1 (This is a little bit outdated but still a very good resource.)
  • NSData
    • https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/Reference/Reference.html
  • Core Data
    • https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/iPhoneCoreData01/Introduction/Introduction.html
    • http://www.raywenderlich.com/934/core-data-tutorial-for-ios-getting-started
    • https://blog.stackmob.com/2012/11/iphone-database-tutorial-part-1-learning-core-data/
  • SQLite in Cocoa
    • https://github.com/ccgus/fmdb (SQLite wrapper)

8.1 More Resources

As promised in some parts of this tutorial here are Links to some more resources on special topics:

  • [MORE 101 NSUSERDEFAULTS]
    • https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/nsuserdefaults_Class/Reference/Reference.html
    • http://mobile.tutsplus.com/tutorials/iphone/nsuserdefaults_iphone-sdk/
  • [MORE ICLOUD STORAGE 102]
    • https://developer.apple.com/library/mac/documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesigningForDocumentsIniCloud.html
    • http://www.raywenderlich.com/6015/beginning-icloud-in-ios-5-tutorial-part-1 (This is a little bit outdated but still a very good resource.)
  • [MORE ICLOUD ENTITLEMENTS 103]
    • http://www.appcoda.com/icloud-programming-ios-intro-tutorial/
    • https://developer.apple.com/library/mac/documentation/General/Conceptual/iCloudDesignGuide/Chapters/iCloudFundametals.html
  • [MORE KVS REFERENCE 104]
    • https://developer.apple.com/library/ios/documentation/Foundation/Reference/NSUbiquitousKeyValueStore_class/Reference/Reference.html

9. References

http://mobile.tutsplus.com/tutorials/iphone/iphone-sdk_store-data/ (2013-09-27)
https://developer.apple.com/library/ios/DOCUMENTATION/Cocoa/Conceptual/PropertyLists/UnderstandXMLPlist/UnderstandXMLPlist.html (2013-09-14)
https://developer.apple.com/videos/wwdc/2012/ Session 209 (2013-09-02)
https://developer.apple.com/library/mac/documentation/General/Conceptual/iCloudDesignGuide/Chapters/Introduction.html (2013-09-05)
http://www.macrumors.com/2013/04/23/icloud-boasts-300m-users-and-20-percent-growth-in-q2-2013/ (2013-09-02)
https://developer.apple.com/library/ios/documentation/General/Conceptual/iCloudDesignGuide/Chapters/Introduction.html (2013-09-29)
https://developer.apple.com/library/mac/documentation/General/Conceptual/iCloudDesignGuide/Chapters/Introduction.html (2013-09-17)
https://developer.apple.com/wwdc/videos/ Session 400 (What’s New in Xcode 5) (2013-09-05)
https://developer.apple.com/library/mac/documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesigningForKey-ValueDataIniCloud.html (2013-09-17)
https://developer.apple.com/wwdc/videos/ Session 207 (What’s New in Core Data and iCloud) (2013-09-05)

9.1 Picture credits

Figure 1, 16
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/UnderstandXMLPlist/UnderstandXMLPlist.html (2013-09-18)
Figure 2, 17
https://developer.apple.com/library/mac/documentation/General/Conceptual/iCloudDesignGuide/Chapters/iCloudFundametals.html (2013-09-18)
Figure 9
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/QuickStartPlist/QuickStartPlist.html (2013-09-18)
Figure 10
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/PropertyLists/AboutPropertyLists/AboutPropertyLists.html (2013-09-18)
Figure 14
https://developer.apple.com/library/mac/documentation/General/Conceptual/iCloudDesignGuide/Chapters/DesigningForKey-ValueDataIniCloud.html (2013-09-18)