Harness Apple's iCloud With Graph

Learn how to synchronize your user's data across all devices.

Application developers are challenged today with building dynamic applications that deliver an engaging experiences across multiple devices. Apple provides many tools to help a developer build next generation applications that are reliable, engaging, and synchronized.

In June of 2011 at WWDC, Steve Jobs introduced iCloud. It was clear at that moment that the game was once again going to change. Developers would be empowered with tools that synchronized user’s data across devices while eliminating the complexity of managing such systems.

In this article, I am going to demonstrate how to use Graph to build a flexible logic layer that notifies your application of changes not only locally, but remotely as well.

What is Graph?

Graph is a semantic database that is used to create data-driven applications. Graph provides an incredibly flexible interface to manage dynamic data. As data changes within your application, whether locally or remotely, notifications are triggered that provide hook points to update your application’s UI or manage application business logic.

Getting Started With Entitlements

To enable iCloud within your application, you need to tell Apple that you will be using the iCloud service, which is done by configuring your application’s entitlements. An entitlement is a configuration that is set in Xcode, which tells Apple that your application will be utilizing a service or product. In this case, we will be setting up iCloud entitlements.

To enable iCloud support in your application, open the capabilities tab in your target panel and click the switch control to ON. You will be asked to choose a development team that is associated with the Apple developer account you are using.

Ensure that the iCloud Documents option is selected in the Services section and use the default container option when selecting a name. You are free at this point to choose another name for your iCloud container if you would like.

Graph Initialization

Graph is initialized and connects to iCloud using the cloud constructor. It sets up iCloud asynchronously to avoid blocking your application's main thread. This is ideal as the data coming in from iCloud may be large in size and could take a while.

import UIKit
import Graph

class ViewController: UIViewController {
    fileprivate var graph: Graph!

    override func viewDidLoad() {
        super.viewDidLoad()
        prepareGraph()
    }
}

extension ViewController {
    fileprivate func prepareGraph() {
        graph = Graph(cloud: "MyContainerName")
    }
}

Cloud constructor

The cloud constructor takes in your iCloud container name that was chosen when setting up iCloud entitlements. When initializing your Graph instance, an optional completion block may be passed, which has two useful parameters. The success (Bool) parameter lets you know if iCloud support is available. The error (NSError?) parameter is an optional parameter that specifies the details of an error when one occurs.

let graph = Graph(cloud: "MyContainerName") { (success, error) in
    if success {
        /* Load UI */
    } else {
        /* Handle error */
    }
}

Graph Delegation

Connecting to iCloud using Graph is a multistep process that is managed internally within the Graph framework. It is important to load your application’s UI and other application business logic at the correct time. The GraphDelegate protocol provides the necessary hook points that allow you to organize your code logic at the correct time.

graph = Graph(cloud: "MyContainerName")
graph.delegate = self

Add the GraphDelegate protocol to your view controller.

extension ViewController: GraphDelegate {
    @objc
    func graphWillPrepareCloudStorage(graph: Graph, transition: GraphCloudStorageTransition) {
        
    }

    @objc
    func graphDidPrepareCloudStorage(graph: Graph) {
        /* Load UI */
    }

    @objc
    func graphWillUpdateFromCloudStorage(graph: Graph) {

    }

    @objc
    func graphDidUpdateFromCloudStorage(graph: Graph) {

    }
}

That’s it. iCloud is now setup with Graph. Your application will now synchronize user’s data across all devices.

Observing Data Changes With The Watch API

To observe data changes, create a Watch instance that observes a type of Graph Node -- an Entity, Relationship, or Action, and set the Watch.delegate property. There are three available Watch protocols, WatchEntityDelegate, WatchRelationshipDelegate, and WatchActionDelegate.

Watch Constructor

The Watch constructor takes a given Graph instance and calls the available delegation protocol methods when a change in data occurs.

To observe data changes, create a Watch instance that observes a type of Graph Node -- an Entity, Relationship, or Action.

import UIKit
import Graph

class ViewController: UIViewController {
    fileprivate var graph: Graph!
    fileprivate var watch: Watch!

    override func viewDidLoad() {
        super.viewDidLoad()
        prepareGraph()
        prepareWatch()
    }
}

extension ViewController {
    fileprivate func prepareGraph() {
        graph = Graph(cloud: "MyContainerName")
        graph.delegate = self
    }

    fileprivate func prepareWatch() {
        watch = Watch(graph: graph)
        watch.delegate = self
    }
}

Watch.for(types: String…)

The Watch.for function takes a list or Array of Graph Node types. For example, to observe changes that surround User Entity types, pass the "User" string as a parameter value.

watch.for(types: "User")

Watch.has(tags: String...)

The Watch.has function takes a list or Array of tags. For example, to observe changes on nodes that have the important or completed tags, pass the "important" and "completed" strings as parameter values.

watch.has(tags: "important", "completed")

Watch.member(of groups: String...)

The Watch.member function takes a list or Array of groups. For example, to observe changes on nodes that are in the admin group, pass the "admin" string as a parameter value.

watch.member(of: "admin")

Watch.where(properties: String...)

The Watch.where function takes a list or Array of properties. For example, to observe changes on nodes that have the username or age properties, pass the "username" and "age" strings as parameter values.

watch.where(properties: "username", "age")

Watch.pause

The Watch.pause function pauses the delegation calls that are executed when changes occur on observed nodes.

watch.pause()

Watch.resume

The Watch.resume function resumes the delegation calls that are executed when changes occur on observed nodes.

watch.resume()