Objective-C in the Cloud

Client Libraries


Introduction

At some point you want to use the (cloud) application you have created. Depending on the type of application you have different options when it comes to picking a technology for your client-side. In the end everything is HTTP and thus every solid HTTP client library can be used to communicate with Objective-Cloud.

Available Libraries

For both, request-based services and invocation-based services you can use almost any HTTP library you want. Here are some ideas:

  • NSURLConnection (comes with OS X/iOS, no dependencies, available since OS X 10.2.7+)
  • NSURLSession (comes with OS X/iOS, no dependencies, available since OS X 10.9/iOS 7)
  • AFNetworking ("A delightful networking framework for iOS and OS X" - open source)

This article does not deal with this libraries. Please follow the above links to get documentation.

For invocation-based services you can use the Objective-Cloud client framework, too. This is discussed in this article.

Invocation-based services tunnels Objective-C messaging. The basic idea is that the service located in the cloud is invoked as usual in Objective-C. This idea gets its full strength, when the client-side encapsulates the underlaying HTTP layer, too. This was enough motivation to develop a client framework. Therefore you can only use it with invocation-based services.

Before you continue

Before you continue reading you should have understood how Objective-Cloud works as described in these documents:

Overview

A use case with Objective-C on both client and server consists of three building blocks:

  • The cloud application and its invocation-based services
  • The client framework for making messaging easy
  • The client application (iOS or OS X) using that framework.

You should download the client SDK now.

The client SDK contains everything you need. When you decompress it you should see a folder with the following structure:

A screenshot which shows the content of the zip

  • Cloud App: Small cloud application the client applications in this tutorial will use.
  • Complete Examples: A workspace containing referring the cloud application and the examples for iOS and OS X after finishing this tutorial.
  • iOS: The static client library for iOS client applications.
  • OS X: The client framework for OS X client applications.
  • Tutotial: The client applications for iOS respectively OS X for starting this tutorial. We put some UI stuff and other code in there, which is not related to the subject of this tutorial.
You cannot pull and push the cloud application because we do not want every customer to change the cloud application.

In addition to the cloud application and the client framework/library there are two pre-configured client applications in the package (one for iOS and one for OS X). They come with a GUI and some boilerplate code. You can find those two projects in ClientSDK/Tutorial/. By having these two projects we can concentrate on describing the client framework/library itself without having the need to also describe how you create buttons. Using these preconfigured Xcode project is optional for the everyday use but mandatory for the purpose of the following two tutorials.

A client framework/library does not have to be written in Objective-C. Any other programming language can be used on the client-side. The level of convenience you get depends on the capability of the used programming language to translate its messaging (or whatever it is called in that programming language) into HTTP. Even though the serve-side of Objective-Cloud is made for and by Objective-C geeks, one of the most important design principles that lead us through the design of Objective-Cloud is that the client side should be programming language agnostic. In other words: Everything that knows HTTP and JSON can talk to Objective-Cloud. We built the client framework for Objective-C, because we are addicted to Objective-C. Additionally we expect that a large part of our customers will use Objective-C on client-side, too. However if you are an expert for another programming language you can write a client framework for that. Let us now!

This article contains complete tutorials for both iOS and OS X. If you are only interested in iOS client applications only, you can skip the next chapter about OS X client applications and proceed here.

OS X Client Application

In this chapter we build a client application running on OS X.

Screenshot of the final client application

Project and Workspace Set-up

The following steps builds up a workspace and links the client application to the client framework.

Building a Workspace

It is a good idea to put the client application together with the cloud application in one workspace. To do so launch Xcode and create a new workspace.

A screenshot which shows the Xcode menu for creating a new workspace

Drag the project for the cloud application and the OS X client application into that workspace.

A screenshot which shows dragging the projects into the workspace

Heads up: Do not make one project a subproject of the other one.

Run Terrasphere. In Xcode select the scheme to the cloud application (Cloud App) and run it with ⌘R. Select the scheme of the client application (OS X Example).

Adding the Client Framework

To link the client application against the client framework, move the client framework located in ClientSDK/OS X into the group Frameworks of the client project OS X Example in Xcode. In the following sheet check the option Copy items into the destination group's folder (if needed).

A screenshot showing the addition of the client framework to the project

Next you have to instruct Xcode to copy the framework into the application bundle. With the project selected in the navigator switch in the editor area to Build Phases. Use the menu Editor | Add Build Phase | Add Copy Files Build Phase to create a new build phase. You can click on the title to change it to Copy Frameworks.

Select Frameworks as destination and move the client framework from the navigator to the file list.

Finally the build phases should look like this:

A screenshot showing the project editor with the final build phases

Service Linking

One of the major design goals of the client framework was to support the full flavor of the Xcode developer tools.

This is accomplished by two things:

  1. The pure API is imported using the cloud interface of the service.
  2. The imported API is then added to a local class.
As explained Objective-Cloud does not rely on the existence of a local counterpart for a service class. Using the client framework a wrapper class is built for offering a better integration into Xcode Developer Tools.

Service Interface Import

To Import the service's interface simply drag the file ServiceCloudInterface.h into the client project. Press and hold ⌘ to only import a reference to the interface file and make sure that the copy button in the following dialog is unchecked. This causes Xcode to import only a reference to the file. This keeps the file always up to date.

A screenshot which shows dragging the cloud interface file into the client project

Wrapper Class

Every remote service gets a local wrapper class. This has two positive effects:

  • Deriving from OCCService adds the messaging infrastructure to the class.
  • Connecting the cloud interface to a local class improves Xcode's code completion and clang support.

Add a class Service derived from OCCService to the client project.

The client framework takes the name of the remote service out of the subclass' name. So the subclass has to be named identically to the class in the cloud application.

In Service.h insert imports for OCClient/Client.h (A) and ServiceCloudInterface.h (B) to make the headers known and add a category to the class that imports the protocol (C). After this, the ServiceCloudInterface.h should look like this:

#import <Cocoa/Cocoa.h>
#import <OCClient/OCClient.h>                               // A
#import "ServiceCloudInterface.h"                           // B

// Subclass with messaging infrastructure
@interface Service : OCCService
@end

// Add service's API
@interface Service (CloudInterface) <ServiceCloudInterface> // C
@end

Connecting the Wrapper to the Service

At runtime the local wrapper class has to be connected to the remote service. You can do this with the method +linkToCloudApp:protocol:handler:, which the service inherited from OCCService. A good place for setting-up the connection is -applicationDidFinishLaunching: (B) after importing the wrapper class' header (A). The application delegate of the client application has a property isLinked, to which we store the execution of the link. The UI is bound to that property to reflect the state for the user. So we set it to update the UI. This is possible, because the completion handler is executed on the main thread.

#import "AppDelegate.h"
#import "Service.h"                                                   // A

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification // B
{
  [Service linkToCloudApp:@"clienttutorial" protocol:@protocol(ServiceCloudInterface) handler:
  ^(__unsafe_unretained Class serviceClass, NSError *error)
  {
    if (serviceClass!=Nil)
    {
      self.isLinked = YES;
    }
    else
    {
      NSLog(@"Could not connect %@. Error %@", NSStringFromClass(serviceClass), error);
    }
  }];
}

After this command the wrapper class is ready to use and the UI is enabled. You can run the client application in Xcode typing ⌘R. The buttons shouldn't be greyed out anymore.

Currently no error is reported, because the implementation of the `linkToCloudApp:…` methods does not try to establish a connection. However you should keep in mind that sending messages over HTTP can break at any time. Therefore you should never rely on a connection after checking its existence. It can be gone in the very next moment.

Sending Messages

There are three APIs for sending messages:

  • Synchronously: Invocation of the cloud application blocks.
  • Asynchonously: Invocation of the cloud application runs in background.
  • Invocation grouping: Invocations are executed blocking but in a group that runs in background.

There is no general rule for a best practice. On a long term view in most cases it would be the most convenient way to use invocation groups. However you should never use the synchronous API on the main thread. This could freeze the UI.

Sending Messages synchronously

The easiest way to invoke a cloud method for simple testing is to send a message synchronously.

Sending the Message

In AppDelegate.m we already put a template for an action method -sendSynchronously:. Browse there and add code to send a message:

- (IBAction)sendSynchronously:(id)sender
{
  NSString *response = [Service sayHello];
  self.outputTextField.stringValue = response;
}

Test the code. Hello should appear in the output text field.

Receiving the Return Value and Errors

As you could see, you get the return value as usual in Objective-C.

But even remote messaging is made as transparent as possible there are always differences: Beside execution time the fact that messaging can fail is the most important issue you have to remember. We solved that problem without changing the API of the cloud interface. Simply ask the response for an error. In this tutorial we show such an error in the output text field:

- (IBAction)sendSynchronously:(id)sender
{
  NSString *response = [Service sayHello];
  if ([response isObjectiveCloudError])
  {
    NSError *error = [response objectiveCloudError];
    response = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
  }
  self.outputTextField.stringValue = response;
}

You can test error handling by changing the name of the service's method in ServiceCloudInterface.h and inside the action method to sayHelloUnknown without recompiling the cloud application. (It wouldn't recompile because the method sayHelloUnknown is not implemented. That's the trick to get an error.) If you did so, please undo your changes before continuing.

Arguments

Of course you can pass arguments to the method. You do it as usual in Objective-C:

- (IBAction)sendSynchronously:(id)sender
{
  NSString *response = [Service sayHelloTo:self.inputTextField.stringValue];
  if ([response isObjectiveCloudError])
  {
    NSError *error = [response objectiveCloudError];
    response = [NSString stringWithFormat:@"Error: %@", [error localizedDescription]];
  }
  self.outputTextField.stringValue = response;
}

Please test the code.

Sending Messages asynchronously

You can use the built-in asynchronous API to send messages asynchronously.

Sending the Message

There is another action method template in AppDelegate.m. Please browse there and paste the below code to -sayHelloSlowlyTo::

- (IBAction)sendAsynchronously:(id)sender
{
  NSString *personName = self.inputTextField.stringValue;
  id<ServiceCloudInterface> invocation = [Service invokeWithHandler:                       // A
  ^(BOOL success, id response) 
  {
    if (!success)
    {
      response = [NSString stringWithFormat:@"Error: %@", [response localizedDescription]];
    }
    self.outputTextField.stringValue = response;
  }];
  [invocation sayHelloSlowlyTo:personName];                                               // B
}

You retrieve an invocation object from the service's wrapper class (A) and send the message to that object (B). For the whole bunch of invoke… methods please refer to the reference.

As you can see, passing arguments is as natural as it is in local code again.

By default an internal queue is used that can run messages simultaneously. Therefore you should keep in mind that the responses may not be in the order the messages are sent.

You might already has recognized that the code uses another cloud method called +sayHelloSlowlyTo:. We did it to make asynchronous execution more visible. So you have to wait a moment for the response of the cloud application. When you test the code, it is a good idea to send a message, add a letter to the input field, resend the message several times to get an impression of the asynchronous execution. Please remark that the responses are not in the order of messaging every time. You see this by the length of the text in the Return Value text field.

Receiving the Return Value and Errors

The return value is passed as an argument to the handler, if the first argument is YES. Otherwise you get an instance of NSError.

As a benefit of the asynchronous API the handler is run on the main thread by default. This let's you change the GUI in the handler.

Invocation Grouping

Sending a bunch of messages makes your code less readable: You have to check for errors after each message. Additionally asynchronous message sending will lead you into nested blocks to make sure that one message is sent after the last one is executed.

To make your code more compact and linear we offer a technology called invocation grouping, which makes it super easy to send a list of messages. An invocation group runs synchronous messages asynchronously with some advantages:

  • You can use the synchronous API with advantages to Xcode's code completion.
  • Even the block to execute is run asynchronously to the caller all messages inside the block are blocking. So the order of messages are as expected.
  • You can launch as many invocation groups as you want.
  • If an error occurs, the return value is nil instead of an instance of NSError. In many cases you can use the messages to nil behavior of Objective-C.
  • In the case of an error all consecutive messages are cancelled.
  • You can ask for errors wherever you want.

Let's have a short example:

[OCCInvocationGroup executeBlock: //  Block is executed asynchronously
^(OCCInvocationContext *context)
{
  // Messages inside the block are blocking
  NSString *aminsGreetings = [Service sayHelloTo:@"Amin"];  // nil on error
  NSString *chrisGreetings = [Service sayHelloTo:@"Chris"]; // nil on error
  NSError *error = [context error]; // One message failed
  if (error!=nil)
  {
    // do error handling
  }
}];

Using a remote Terrasphere

When you are done with local testing you want to run your client against the cloud application hosted at our servers. You simply have to add one single message at the beginning of +applicationDidFinishLaunching: before linking the class:

[OCCService setUsesRemoteHost:YES];

If you use a dedicated Mac for terrasphere hosting, please set the domain of it:

[OCCService setLocalHost:@"Amins-MacBook-Air.local:10000"];
[OCCService setUsesRemoteHost:NO];

10000 is the default port number of terrasphere.

Hosting your own Objective-Cloud infrastructure for production you have to set its domain before switching to the remote host:

[OCCService setRemoteHost:@"example.com"];
[OCCService setUsesRemoteHost:YES];

iOS Application

If you want to develop a client application for iOS then you should read the following paragraphs.

Building a Workspace

Get the iOS Xcode Tutorial Project

We have prepared an Xcode project just for this tutorial. This Xcode project is nothing special. It merely contains a view controller, a little bit of UI (two buttons and one text field) and two action methods that will be implemented in the course of this tutorial. The Xcode tutorial project comes with the client framework. It contains a folder called Tutorial which contains the tutorial projects for iOS and OS X. So open the iOS Example Xcode project.

Next you have to link against the client static library. The folder in the client framework which is called iOS contains the static client library libOCClient.a and the header files of the client framework (in the include folder). Use Finder to copy libOCClient.a and the folder include in the same directory where the Xcode project resides.

Great. Now drag libOCClient.a in the Frameworks group of the Xcode project.

Modify the build settings (NOT OPTIONAL)

Since the client library creates a few category methods internally you have to modify the build settings of your Xcode project and tell the linker to load the category methods.

  1. Select your Xcode project in the Project Navigator.
  2. Select your Xcode project in the Projects and targets list.
  3. Go to the Build Settings tab.
  4. Search for other linker flags.
  5. Add a new entry to the Other Linker Flags build setting which contains the following value: -ObjC
  6. Search for Header Search Paths and add a new entry to it: $SOURCE_ROOT/include

Congratulations. The project that is correctly linking agains the client library.

Workspace Creation

Remember: Each cloud application usually has a cloud or service interface. This is a normal header file with a @protocol which exposes methods that can be invoked remotely. In a real word scenario you would now drag the Xcode project that contains your client app in the workspace of your cloud app. For the purpose of this tutorial you should use the cloud application that we have prepared for this tutorial.

  1. Find the cloud app Xcode project: It is in the folder named Cloud App.
  2. Create a new workspace and put the Cloud App Xcode project and the iOS Example Xcode project to it.

Service Linking

The remote service on the cloud is represented locally by a stand-in class. We have to connect the local class to the cloud application's class.

Import the Cloud Interface

Find ServiceCloudInterface.h in the Cloud App project, select it and drag it over to the example project. Make sure to create a reference and not a copy to this file.

Check your work by using the following screenshot as a reference.

A screenshot which shows the new workspace structure

You have now single workspace that contains both: your cloud app and your client app.

Wrapper Class

In your client project you now have to create a wrapper class. The wrapper class has to be a subclass of OCCService and it has to indicate that it implements the cloud interface ServiceCloudInterface. Now add a new class to your client project. Name it Service and set it super class to OCCService. Paste the following code in Service.h:

#import <OCClient/OCClient.h>
#import "ServiceCloudInterface.h"

// Subclass with messaging infrastructure
@interface Service : OCCService
@end

// Add service's API
@interface Service (CloudInterface) <ServiceCloudInterface>
@end

Leave the implementation of this class as is.

At runtime you have to link your wrapper class to a remote service. A good place to do that is either in your application delegate or in one of your view controllers. In this tutorial we will perform the linking in our root view controller: ViewController.m:

#import "ViewController.h"
#import "Service.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *input;
@property (weak, nonatomic) IBOutlet UILabel *output;
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
    
  [Service linkToCloudApp:@"clienttutorial"
                 protocol:@protocol(ServiceCloudInterface)
                  handler:^(__unsafe_unretained Class serviceClass, NSError *error) {
                    NSLog(@"Linked!");
                  }];
}

- (IBAction)sendSynchronously:(id)sender {}
- (IBAction)sendAsynchronously:(id)sender {}

@end

As you can see there linking only involves calling the method +linkToCloudApp:protocol:handler:. Please refer to the API documentation for the details.

Sending Messages

After linking the local wrapper class to the remote service, you can send messages to the cloud application.

Sending Messages synchronously

Now that the link between your wrapper class and the remote service is established you can use the library to send messages. Sending a message synchronously is highly convenient:

- (IBAction)sendSynchronously:(id)sender {
  self.output.text = [Service sayHelloTo:self.input.text];
}

The code above is omitting the error handling. A better version of the above code would look like this:

- (IBAction)sendSynchronously:(id)sender {
  NSString *response = [Service sayHelloTo:self.input.text];
  if(response.isObjectiveCloudError) {
    NSLog(@"error: %@", response);
  } else {
    self.output.text = response;
  }
}

Now you should perform a first test.

  1. Paste the implementation of -sendSynchronously: in ViewController.m.
  2. Start the Cloud App: Select the CloudApp target and hit ⌘R.
  3. Start the client app in the simulator: Select the iOS Example target and hit ⌘R.
  4. Enter something in the text field and tap on Send Sync.

Sending Messages asynchronously

Sending messages asynchronously is easy as pie. Replace sendAsynchronously: with the following implementation in ViewController.m:

- (IBAction)sendAsynchronously:(id)sender {
  id<ServiceCloudInterface> invocation = [Service invokeWithHandler:^(BOOL success, id value) {
    if(success) {
      self.output.text = value;
    }
  }];
  [invocation sayHelloTo:self.input.text];
}

Using Invocation Groups

Sending a bunch of messages makes your code less readable: You have to check for errors after each message. Additionally asynchronous message sending will lead you into nested blocks to make sure that one message is sent after the last one is executed.

To make your code more compact and linear we offer a technology called invocation grouping, which makes it super easy to send a list of messages. An invocation group runs synchronous messages asynchronously with some advantages:

  • You can use the synchronous API with advantages to Xcode's code completion.
  • Even the block to execute is run asynchronously to the caller all messages inside the block are blocking. So the order of messages are as expected.
  • You can launch as many invocation groups as you want.
  • If an error occurs, the return value is nil instead of an instance of NSError. In many cases you can use the messages to nil behavior of Objective-C.
  • In the case of an error all consecutive messages are cancelled.
  • You can ask for errors wherever you want.

Let's have a short example:

[OCCInvocationGroup executeBlock: //  Block is executed asynchronously
^(OCCInvocationContext *context)
{
  // Messages inside the block are blocking
  NSString *aminsGreetings = [Service sayHelloTo:@"Amin"];  // nil on error
  NSString *chrisGreetings = [Service sayHelloTo:@"Chris"]; // nil on error
  NSError *error = [context error]; // One message failed
  if (error!=nil)
  {
    // do error handling
  }
}];

Using local Terrasphere from a Device (iPhone, iPad)

Before you run the client application on your iPhone/iPad for testing purposes you have to set the address of the Mac running Terrasphere. You do this by sending +setLocalHost: to OCCService.

[OCCService setLocalHost:@"hostName_of_your_mac:10000"];

You can determine the host name of your Mac by running hostname in Terminal. Use the output from hostname and add :10000 to it.

The good news is: You have to do this only one time. When switching back to the simulator, this setting is simply ignored.

Using a remote Terrasphere

When you are done with local testing you want to run your client against the cloud application hosted at our servers. You simply have to add one single message before linking the class:

[OCCService setUsesRemoteHost:YES];

Hosting your own Objective-Cloud infrastructure you additionally have to set its domain before switching to the remote host:

[OCCService setRemoteHost:@"example.com"];
[OCCService setUsesRemoteHost:YES];