How to create save eventually functionality in iOS

If you used Parse as the backend for an iOS app you will likely have made use of the saveEventually method in the Parse iOS SDK. With this method you could allow your users to take actions that require an internet connection without worrying about whether or not their device was connected at the time. Calling saveEventually would execute the action immediately or, if the device was not connected, when it regains connectivity.

When Parse announced that it would shut down I transitioned from Parse across to AWS. AWS is incredibly feature-rich and overall I am very satisfied with it. However, one thing AWS does not offer is an equivalent of Parse’s saveEventually method. After the searching the internet and failing to find a viable replacement, I decided to build one myself. This article explains how I did it.

The article has four sections:

  1. Creating tasks
  2. Setting up Core Data
  3. Storing and executing tasks
  4. Example

01. Creating tasks

In this article I use the word “task” to refer to some action to be performed by an app that requires an internet connection. A simple example is sending data to a remote server. If a task is created when the device is not connected it must be stored until the device regains connectivity. How can we store arbitrary tasks for later retrieval and execution?

A task is just a series of actions (methods) and data (properties). So we can encapsulate it in an object and store it in Core Data. To store an object in Core Data we must make it serializable by implementing NSObject and NSCoding. See this article for an excellent discussion of serialization in iOS.

This class sets out the basic structure of the task object:

The execute() method will contain the code that executes the task (eg. sending information to the server). It accepts three closures, only one of which should be called when the task completes:

  • successCompletionHandler: call if the task completes successfully

  • connectionErrorCompletionHandler: call if the task fails because of a connection error

  • otherErrorCompletionHandler: call if the task fails because of some other error (eg. a runtime error)

The init?() and encode() methods are required by NSObject and NSCoding, and are used to serialize and deserialize the object.

WhenReachable is intended to be an abstract class. The idea is that we inherit from it to create a subclass with specific functionality. For example, a subclass for sending data to the server might look like this:

02. Setting up Core Data

To store task objects I created a new Core Data entity called “TaskWhenReachable” and gave it two attributes date and task, as shown in Figure 1.

Figure 1: Attributes of TaskWhenReachable entity.

Note that the task attribute has the type “Transformable”. This is the Core Data type that accepts serialized objects. The date attribute will be used to ensure that pending tasks are executed in the same order they were created.

To avoid runtime errors and enable autocompletion for Core Data entities, it’s good practice to create an NSManagedObjectSubclass. Xcode can do this for you automatically. Select the TaskWhenReachable entity in your data model and then go to the Data Model inspector tab and ensure that Codegen is set to “Manual/None”:

Figure 2: Data Model inspector

Then go to the Editor menu and select “Create NSManagedObject Subclass…”. Xcode will create two files for you: TaskWhenReachable+CoreDataClass.swift and TaskWhenReachable+CoreDataProperties.swift. Now the TaskWhenReachable entity can be used like this:

03. Storing and executing tasks

For any particular task that requires an internet connection, we want to do the following:

  • if the device does not currently have an internet connection, store the task in Core Data
  • otherwise, execute the task along with all tasks previously stored in Core Data

So we need a way to check for internet connectivity, and we need methods to add and remove tasks from Core Data.

To check for internet connectivity I use the Reachability framework, which is available here along with instructions on how to install it. Once installed, I added the following to the AppDelegate so that Reachability is accessible throughout the app:

These methods, also added to the AppDelegate, are used to add and remove tasks from Core Data:

Note that addTaskToCoreData() and getNextTaskAndDeleteFromCoreData() deal with objects of the generic type WhenReachable. This allows them to work with all of WhenReachable’s subclasses.

Now we need a method to control the retrieval and execution of tasks:

This works as follows. We pull the first (ie. oldest) task from Core Data and then check for an internet connection. If the device is connected, we call the task’s execute() method, otherwise we put it back in Core Data. If execution fails because of a connectivity problem (eg. the connection dropped out during execution), we put the task back in Core Data so we can try again in the future. If execution succeeds or if it fails for another reason, we call tryExecuteTasks() again, effectively moving on to the next task stored in Core Data (if there is one).

When a task is created, we want to execute it immediately if the device has connectivity. However, we also want to execute all other pending tasks, and we want to do so in chronological order. Since tryExecuteTasks() executes tasks in chronological order, the simplest way to handle this is as follows:

When a new task is created, we pass it to scheduleTask(). This adds the task to Core Data and then, after checking for internet connectivity, calls tryExecuteTasks() to execute all pending tasks in chronological order (including the one we just added to Core Data). If there is no internet connectivity the tasks are retained in Core Data and the app will try again the next time a task is created and scheduleTask() is called.

One of the nice things about Reachability is that it ships with methods that make it easy to monitor for changes in internet connectivity. With just a few lines of code we can make the app automatically check for internet connectivity and execute pending tasks at the next opportunity:

The closure that we have assigned to reachability.whenReachable will be executed each time the app regains connectivity from a previously unconnected state. We simply call tryExecuteTasks() to execute all pending tasks.

04. Example

Say we want to send the string “Hello” to the server. We can do it like this:

If the device has connectivity the string will be sent to the server immediately. If not, it will be sent to the server as soon as the app regains connectivity.

We now have a fully functioning save eventually system for iOS.

Download the sample iOS app.

Got a question or found a bug? Please help to improve this article by leaving a comment below.

Leave a reply

Your email address will not be published. Required fields are marked *