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.
For both, request-based services and invocation-based services you can use almost any HTTP library you want. Here are some ideas:
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 reading you should have understood how Objective-Cloud works as described in these documents:
A use case with Objective-C on both client and server consists of three building blocks:
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:
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.
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.
In this chapter we build a client application running on OS X.
The following steps builds up a workspace and links the client application to the client framework.
Drag the project for the cloud application and the OS X client application into that workspace.
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).
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).
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:
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:
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.
Every remote service gets a local wrapper class. This has two positive effects:
OCCService
adds the messaging infrastructure to the class.Add a class Service
derived from OCCService
to the client project.
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
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.
There are three APIs for sending messages:
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.
The easiest way to invoke a cloud method for simple testing is to send a message synchronously.
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.
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.
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.
You can use the built-in asynchronous API to send messages asynchronously.
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.
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.
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:
nil
instead of an instance of NSError
. In many cases you can use the messages to nil behavior of Objective-C.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
}
}];
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];
If you want to develop a client application for iOS then you should read the following paragraphs.
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.
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.
Project Navigator
.Projects and targets list
.Build Settings
tab.other linker flags
.Other Linker Flags
build setting which contains the following value: -ObjC
Header Search Paths
and add a new entry to it: $SOURCE_ROOT/include
Congratulations. The project that is correctly linking agains the client library.
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.
Cloud App
.Cloud App
Xcode project and the iOS Example
Xcode project to it.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.
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.
You have now single workspace that contains both: your cloud app and your client app.
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.
After linking the local wrapper class to the remote service, you can send messages to the cloud application.
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.
-sendSynchronously:
in ViewController.m
.Send Sync
.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];
}
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:
nil
instead of an instance of NSError
. In many cases you can use the messages to nil behavior of Objective-C.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
}
}];
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.
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];