Submitting an update to the App Store is unavoidable if you want to add major enhancements to an application. However, it is cumbersome and time-consuming to go through the App Store review process for simple modifications. In this article, I will show you how to update an application remotely using the GroundControl project.
Introduction
The idea behind GroundControl is simple, the application fetches a remote property list and updates its NSUserDefaults
with the contents of the property list. Even though many applications download data from a dedicated backend to dynamically update their contents, the strategy that I will lay out in this article is ideal if a dedicated backend isn't an option or exceeds your application's requirements.
The setup discussed in this tutorial is lightweight and inexpensive. Not only is it inexpensive, it is also easy to maintain, which is a factor often overlooked by both clients and developers.
It is important to emphasize that property lists are ideal for small chunks of data and shouldn't be used as a replacement for a dedicated backend. If an application needs to be updated on a daily basis with new content then the strategy dicussed in this tutorial won't cut it.
To illustrate how all the different pieces fit together, we will build an iOS application appropriately named Remote Control. Even though the application itself won't do much, by integrating GroundControl to update the application's NSUserDefaults
it will become clear how easy it is to remotely update an application with very little overhead. Let's get started.
Step 1: Project Setup
Create a new project in Xcode by selecting the Single View Application template from the list of templates (figure 1). Name your application Remote Control, enter a company identifier, set iPhone for the device family, and check Use Automatic Reference Counting. Make sure to uncheck the remaining checkboxes for this project. Tell Xcode where you want to save your project and hit the Create button (figure 2).
Step 2: GroundControl
The name Mattt Thompson might sound familiar to you. He is one of the creators and the driving force behind AFNetworking, the most popular networking library for iOS and OS X. GroundControl is built on top of AFNetworking and provides an easy solution to remotely configure an application by updating the application's NSUserDefaults
. It does this through a category on NSUserDefaults
, which allows NSUserDefaults
to update itself asynchronously with a remote property list.
It doesn't matter where the property list is located as long as it can be reached through the HTTP(S) protocol and the content type of the response is set to application/x-plist
.
On the GitHub page of GroundControl, Mattt provides two examples of how to implement the server side of the story, one Ruby implementation and one Python implementation. However, I promised you that the setup would be lightweight and inexpensive. Instead of using a server that you need to maintain, we will be using Amazon's Simple Storage Service or S3. As I explained earlier, the requirements are simple, that is, (1) the file needs to be a property list and (2) the content type of the response needs to be application/x-plist. Amazon's S3 is a perfect fit.
Not only is AWS S3 perfectly suited for serving up static files, it has earned its stripes over the years. Even if your application reaches the top 10 in the App Store, S3 will keep serving up the property list this without problems. Before heading over to the AWS (Amazon Web Services) Console, your first need to integrate GroundControl into your project.
Step 3: Adding AFNetworking and GroundControl
GroundControl is built on top of AFNetworking, which means that you need to add AFNetworking to your Xcode project. Even though we could add AFNetworking and GroundControl using CocoaPods, we will add both libraries manually since some of you may not be familiar with CocoaPods. Download the latest version of AFNetworking and GroundControl and drag each library into your Xcode project. Make sure to copy the contents of the folder into your project by checking the checkbox labeled Copy items into destination group's folder (if needed) and don't forget to add both libraries to the Remote Control target (figures 3 and 4).
AFNetworking relies on the System Configuration and Mobile Core Services frameworks for some of its functionality. It is therefore necessary to link your project against those frameworks. Select your project in the Project Navigator and select the Remote Control target from the list of targets. Choose Build Phases at the top, open the Link Binary With Libraries drawer, and add both frameworks to your project (figure 5).
Before we can start working with AFNetworking and GroundControl, we need to update the project's Prefix.pch file by adding an import statement for the frameworks we just added to the project. Take a look at the snippet below for clarification.
// // Prefix header for all source files of the 'Remote Control' target in the 'Remote Control' project // #import <Availability.h> #ifndef __IPHONE_4_0 #warning "This project uses features only available in iOS SDK 4.0 and later." #endif #ifdef __OBJC__ #import <UIKit/UIKit.h> #import <Foundation/Foundation.h> #import <MobileCoreServices/MobileCoreServices.h> #import <SystemConfiguration/SystemConfiguration.h> #endif
Step 4: Integrating GroundControl
We start by adding two import statements to the application delegate's implementation file, one for AFNetworking and another for GroundControl. If you end up using AFNetworking in various places in your project then it might be a good idea to add the import statement for AFNetworking to your project's Prefix.pch file.
#import "AFNetworking.h" #import "NSUserDefaults+GroundControl.h"
Integrating GroundControl is the easy part as it only requires two lines of code. In the application delegate's applicationDidBecomeActive:
method, we call a helper method to initialize GroundControl.
- (void)applicationDidBecomeActive:(UIApplication *)application { // Initialize GroundControl [self initializeGroundControl]; }
The helper method couldn't be easier thanks to the category on NSUserDefaults
provided by GroundControl. We create the URL for the property list and pass it as an argument in registerDefaultsWithURL:
. This will ensure that the property list is downloaded asynchronously. Once downloaded, NSUserDefaults
is automatically updated with the contents of the property list.
- (void)initializeGroundControl { NSURL *url = [NSURL URLWithString:@"https://www.example.com/RemoteControl.plist"]; [[NSUserDefaults standardUserDefaults] registerDefaultsWithURL:url]; }
Another option is to call registerDefaultsWithURL:success:failure:
instead if you prefer to work with success and failure callbacks. For even more control, GroundControl provides a third method, registerDefaultsWithURLRequest:success:failure:
. This method accepts an instance of NSURLRequest
as well as a success and failure block.
It might surprise you, but integrating GroundControl was the difficult part. The final two steps are (1) creating the property list and (2) uploading it to Amazon's S3.
Step 5: Create a Property List
Create a new file in Xcode by selecting the Resource tab on the left and choosing Property List on the right (figure 6). Name the file RemoteControl and save it to your computer's desktop. It is not necessary to add it to your Xcode project. The easiest way to edit the property list is in Xcode. Take a look at the screenshot below to see how the property list looks like and can be edited in Xcode (figure 7). Keep in mind that a property list can only contain booleans, dates, numbers, binary data, strings, arrays, and dictionaries. It is good practice to namespace the keys (for example, with the project's class prefix) to make sure they don't conflict with any other entries stored in NSUserDefaults
.
Step 6: Upload the Property List
To use Amazon's Web Services, you need an Amazon account. The costs associated with Amazon's S3 are extremely low and chances are that you won't even be charged for quite some time since the property list is only a few kilobytes in size.
You can upload the property list to Amazon S3 using Amazon's AWS Console or by using an application that supports Amazon's S3, such as Panic's fantastic Transmit.
After uploading the property list, there are three things left to do, (1) copying the URL of the property list, (2) granting public access to the property list, and (3) setting the correct content type.
Copy the URL
In the AWS Console, select the property list and click the Properties button in the top right. Select the tab named Details in the properties panel (figure 8). Copy the link of the property list and update the initializeGroundControl
method in the application delegate with the correct URL.
Grant Public Access
Select the tab named Permissions in the properties panel and add a new set of permissions to the property list by clicking the Add more permissions button. Set the grantee to Everyone and limit the permissions to Open/Download. This will make sure that your application can download the property list. Don't forget to click the save button to propagate the changes.
Set Content Type
Select the tab named Metadata in the properties panel and set the value of the key Content-Type to application/x-plist. Don't forget to click the save button to propagate the changes.
Step 7: Build and Run
Your application is now ready to be updated remotely. How your application responds to the changes made by the property list depends on your application. This article only shows you how easy it is to remotely update an application without having to submit a new version to the App Store.
You can test if everything works by slightly modifying the initializeGroundControl
method. Instead of invoking registerDefaultsWithURL:
, we use registerDefaultsWithURL:success:failure:
provided by GroundControl. If you have implemented GroundControl correctly, the dictionary of the success block should contain the data of the property list. Your application's NSUserDefaults
will be updated automatically.
- (void)initializeGroundControl { NSURL *url = [NSURL URLWithString:@"https://s3.amazonaws.com/com.mobiletuts.bartjacobs/RemoteControl.plist"]; [[NSUserDefaults standardUserDefaults] registerDefaultsWithURL:url success:^(NSDictionary *defaults) { NSLog(@"Defaults > %@", defaults); } failure:^(NSError *error) { NSLog(@"Error > %@ with user info %@.", error, [error userInfo]); }]; }
Dropbox
The immense success and fast adoption rate of Dropbox has resulted in the emergence of numerous clever and useful applications. For people not familiar with AWS and Xcode, it might be easier to work with Dropbox. Even though Dropbox is a great solution and provides the option to make a file publicly accessible by placing it in the public Dropbox folder, Dropbox cannot be used with the current version of GroundControl. The reason is that the content type of a property list served by Dropbox is automatically set to text/plain instead of the required application/x-plist. AFNetworking throws an error due to the fact that the required content type does not match application/x-plist.
However, it is possible to use Dropbox to remotely update an application. The caveat is that you will need to do some of the heavy lifting yourself, that is, asynchronously downloading the property list and updating the application's NSUserDefaults
.
Despite the usefulness of the public Dropbox folder, its future is uncertain. It is also unclear if there is a bandwith limit imposed on files in the public Dropbox folder. In other words, if you wish to use Dropbox for remotely updating an iOS or OS X application, keep in mind that it might break at some point or that your Dropbox account might get suspended due to exceeding certain limitations.
Conclusion
It isn't always necesarry to build a custom backend for an iOS or OS X application if all you need to do is update a handful of settings from time to time. The strategies discussed in this article demonstrate how to remotely update an application with a simple property list.
Of course, there are several other possibilities to remotely update an application. For example, it is possible to use a service like Parse. However, I find that even Parse is "too much" if all you want to do is update a handful of settings.
Comments