Objective-C in the Cloud

Invocation based Services

This article describes concepts and features with regards to invocation based applications. If you have never worked with invocation based applications before you should check out the tutorial.

As an Objective-C developer you are familiar with two basic concepts of object-orientated, class based programming languages:

  • Objects are constructed from classes. Classes can inherit from a super class. This forms an inheritance tree.
  • Objects communicate with messages. A message can have arguments and a return value.

Objective-Cloud brings this concepts to the cloud. This means that you can send a message to an object located on our servers, whose construction plan is stored in a class.

When to use Invocation based Applications

In most cases invocation based services are the first choice. They are very similar to what you know from your daily development routine. Especially when your client is developed in Objective-C as well, you should use invocation based services.

The forthcoming client framework supports message sending to the invocation bases services. You do not have to care about the transport any more.

However there are reasons that are less optimal with invocation based services. You should not use invocation based services,

  • if you want to generate a response that is not JSON (HTML, raw image data, …) or
  • if the communication between client and server heavily uses HTTP features (like cookies, sessions, caching, custom headers, …)

Take in account, that Objective-C messaging is neutral to the transport. Using HTTP features heavily simply means that you add a secret information. It is a better way to put that information on a parameter instead of HTTP.

Core Concepts

There are several core concepts you should understand:

Universality

Objective-Cloud is created for Objective-C developers. That means, that when developing something for Objective-Cloud you do that by using Objective-C.

On the other hand services running on Objective-Cloud should be usable by everybody. That means, that the public interface of a service should not relay on anything that is specific to Objective-C. Objective-Cloud solves this by using two mature technologies: JSON for the content and HTTPS for the transport. The reality is that everybody is able to send a HTTP request which contains a JSON document. Relying only on JSON and HTTP has some benefits:

  • Even though the implementation is done by using Objective-C you can develop clients for different systems (iOS, Android, Windows, web services with Ruby or PHP, etc.)
  • Since your code is running on OS X you can write services and use technologies that are specific to OS X. This allows you to use OS X specific functionality (Core Image, Core Video, PDFKit) on other platforms. It is worth to mention that this applies to iOS, too, because some technologies available on OS X do not exist on iOS.

Stateless and stateful Services

By now Objective-Cloud supports stateless services: An (remote) invocation causes a method to be executed that returns a value. No state is stored for the next message, no side effects exist. This makes it possible to parallelize the execution of your code.

Of course, you can have state by passing instance objects as arguments to a service and the service can deliver a new state using instance objects as return value or via changed arguments parameters. Another pattern is to store a state in a data base as supported by Objective-Cloud.

Roadmap: We are working on stateful services that work out of the box by storing and fetching objects automatically to the database.

There is an object oriented programming paradigm which says that states are stored in objects. For Objective-C this applies to (stateful) instance objects, but not to class objects, which are stateless. Consequently the receiver of a message is a class object. This is possible, because class objects are first class objects in Objective-C and support polymorphism and late binding. In contrast to other programming languages there is no need to introduce an anti-conceptual "state-losing stateful objects" (like Servlets) to get the basic features of object oriented programming.

Client-Server Architecture

Objective-Cloud uses a strict client-server architecture: A message is sent from a client to the server to executed code on the server. This corresponds to method invocations on a local machine.

Because of this strict concept, arguments are passed by value (they are copied) within a message invocation. Methods invoked on arguments are always executed on the server’s copy. After executing a method, you can send the argument automatically back to the client as described [here].

Comparison to distributed Objects and XPC Services

Maybe you are familiar with distributed objects (DO) and XPC services (XPC), technologies already existing in Cocoa. But because of the nature of Objective-Cloud there are some differences:

  • There is no "send back": When you send a message to a method on the server with an argument, a message to the argument will not result in a message to the instance on the client side. It will be send to the server's copy of the argument.
  • In contrast to DO and XPC we cannot assume, that both sides have the same classes. We even do not know, whether the client is an application developed in Objective-C.
  • Both sides of a DO and XPC application can trust in each other. Both are under your control. A wrong argument type, for example, is simply a mistake (producing a warning at compile time) you made and that you can fix. On Objective-Cloud everybody can potentially send messages to your service, especially with wrong argument types. Objective-Cloud handles that for you automatically and rejects that messages instead of producing a runtime error while executing the method. On the other hand id typed arguments are still possible.

Creating a Service

An invocation based service is always provided by a class and its methods. Such a class is called a public class, invocable methods are called public methods (public in the context of Objective-Cloud always means "reachable from a client's point of view").

The implementation of a service consists of three parts:

  • A protocol for the cloud interface that publishes methods. It contains the methods that should be reachable.
  • A class interface for the service class.
  • A implementation of the service class.

All these components are standard Objective-C well-known to you.

Cloud Interface

The cloud interface is a protocol that contains the methods that should be reachable from clients. These are called the public methods.

1 // ServiceCloudInterface.h
2 #import <OCFoundation/OCFoundation.h> // imports <Foundation/Foundation.h>
3 
4 @protocol ServiceCloudInterface <NSObject>
5 + (NSString*)concat:(NSString*)firstPart with:(NSString*)secondPart;
6 @end

By convention the name of a protocol acting as a cloud interface ends with CloudInterface. The Objective-Cloud runtime environment relies on this convention and accepts only methods declared by protocols following this convention. The publicity of methods from the base class is automatically inherited. It is not necessary to include them in the cloud interface of the subclass.

If your a class implements a cloud interface specific to a class, it is best practice to name the cloud interface like the class and append `CloudInterface`. If a cloud interface is used by more than one class – like usual protocols in Objective-C – you can choose a different name, but the suffix still has to be `CloudInterface`.

// Cloud interface of a class 'Service'
@protocol ServiceCloudInterface <NSObject>
+ (NSString*)concat:(NSString*)firstPart with:(NSString*)secondPart;
@end

// Cloud interface not related to a specific class
@protocol XMLCloudInterface <NSObject>
+ (NSDictionary*)xmlRepresentation
@end

// A class using its own and a common cloud interface
@interface Service : OCFPublicObject <ServiceCloudInterface, XMLCloudInterface>
@end

Class Interface

The interface of a public class is formed as usual. There are only two things that distinguish an interface of a public class from a "normal" class: It inherits from OCFPublicObject and it implements a cloud interface.

// Service.h
#import "ServiceCloudInterface.h"

@interface Service : OCFPublicObject <ServiceCloudInterface>
@end

You can add methods to the class interface that should be public inside the cloud application, but private to the client. A public class can have more than one cloud interface.

// Service.h
#import "ServiceCloudInterface.h"

@interface Service : OCFPublicObject <ServiceCloudInterface, ActivityCloudInterface>
@end

As you can see, the Service is derived from OCFPublicObject.

Change incoming: This will change in near future: Every class can export a cloud interface. So you should not take any advantage of being the public class a subclass of OCFPublicObject. (I. e. expecting messages to super to be executed inside OCFPublicObject or using `OCFPublicObject` as a stop class for iterations.)

Implementation

You implement the public class as usual in Objective-C by adding class methods:
// Service.m
#import "Service.h"

@implementation Service
+ (NSString*)concat:(NSString*)firstPart with:(NSString*)secondPart
{
   return [firstPart stringByAppendingString:secondPart];
}
@end

Parametrization

Every code needs some data to process. The usual way to provide data for a method is to set its arguments. Beside this you can retrieve information from the HTTP request itself.

Parameters

Passing arguments to a parameter of a public method is done as usual: By default the runtime environment of Objective-Cloud reads the argument for every parameter from JSON and passes it to the method. The number and types of arguments are checked. (See "Typing" for more information about supported types.) If this check fails, the invocation is rejected.

Indirect Parameters

If a parameter has an indirection (indirect out parameter), it is assumed, that no argument for it is pushed to the method, but the method returns a value. This is well-known for Cocoa's error handling pattern:
+ (NSString*)methodThatCanFailWithError:(NSError**)error
{
   
   if( somethingFailed )
   {
      *error = [NSError ]; // Pass this object back to the sender
      return nil;
   }

   return ;
}
In this case error points to an object reference and *error equals nil. If you want to be such a parameter bidirectional, you have to mark it with inout.
+ (NSString*)methodThatCanFailWithError:(inout NSError**)error
{
   
   if( somethingFailed )
   {
      *error = [NSError ]; // Pass this object back to the sender
      return nil;
   }

   return ;
}

Changed Argument

Another way to propagate data back to a sender is to change an object, which is an argument:
+ (void)methodThatChanges:(NSMutableString*)arg
{
   [arg appendString:@" Negm"];
}

This is a patter to avoid in a local application. The sender of a message cannot recognize, whether an argument is changed inside a method or not. It might be easy in this case, because there is no other reason to pass an instance of NSMutableString instead of NSString. But this only applies to a hand-full classes that build a mutable/immutable class pair. Anyway, it is supported with Objective-Cloud.

By default Objective-Cloud takes the argument from JSON, but does not send the argument back to the sender of the original message. Any change to the argument is made locally on the server. To send the changed object back, you have to mark that with inout:

+ (void)methodThatChanges:(inout NSMutableString*)arg
{
   [arg appendString:@" Negm"];
}

Return Value

The usual and most convenient way to return a result of the processing is to use C's return. As you can see in the samples, there is nothing unusual about it. You do not have to use completion blocks and other boiler plate code to make things work. (For asynchronous responses see Responding asynchronously.)

Invocation Object

For every invocation an instance object of OCFPublicInvocation is created. You can access it with +currentInvocation (OCFPublicInvocation). There are some reasons for doing so:

  • You want to respond asynchronously.
  • You want to respond something else than JSON (i. e. HTML or binary data).
  • You want to read properties of the HTTP request (i. e. cookies).
  • You want to set properties of the HTTP response (i. e. cookies ).

Responding Asynchronously

If your code inside the method uses an asynchronous API, it is inconvenient to return a result in C style:

+ (NSString*)methodUsingAsyncAPI
{
  __block NSString* returnValue;

  dispatch_semaphore_t semaphore = dispatch_semaphore_create(0L);

  [Helper doSomethingAsyncWithCompletionHandler:^(NSString* result)
  {
    returnValue = result;
    dispatch_semaphore_signal(semaphore);

  }];
  dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

  return returnValue;
}

Therefore you can tell the Objective-Cloud's runtime environment to ignore the (synchronous) return value and provide a return value asynchronously to make the code more readable. To do so, you read the invocation object and send the respond using this. This strategy is told to the runtime environment by returning an ignore value.

+ (NSString*)methodUsingAsyncAPI
{
   OCFPublicInvocation *invocation = [self currentInvocation]; 
   [Helper doSomethingAsyncWithCompletionHandler:^(NSString* result)
   {
      [invocation returnJSONWithReturnValue:result];
   }];
   return OCFResponsesUsingRequest;
}

It is important that you read the invocation object in the synchronous control flow of your method. If you try to retrieve it in the – potentially asynchronous – block, it may fail.

All public methods run asynchronously and do not block. So you do not have to dispatch a block asynchronously, if it is a long runner. We have already done this for you.

Responding Non-JSON

Sometimes you do not want to encapsulate the return value in JSON. You are free to return any data, if you want to. The invocation object has some methods for doing so:

+ (NSString*)sayHello
{
   OCFPublicInvocation *invocation = [OCFPublicInvocation currentInvocation];
   [invocation returnPlainTextWithString:@"hello\n"];
   return OCFResponsesUsingRequest;
}

This method responds

hello\n

instead of

{
   returnValue : "hello\n"
}

OCFPublicInvocation has convenient methods for other types including HTML and binary data.

Using the HTTP Request

You have access to the underlying HTTP request via +request (OCFPublicInvocation). It returns the raw OCFRequest object as you will get as a parameter in a handler block developing a request based service. This offers you the complete abilities of request based applications for receiving requests and sending responses.

+ (void)requestBasedMethod
{
   OCFPublicInvocation *invocation = [OCFPublicInvocation currentInvocation];
   OCFRequest *request = invocation.request;
   // …
}

In Objective-C data exchange in a message is done with arguments and return values. The message and invocation mechanism is transparent. If you heavily use properties of the request and its response in invocation based services, you probably do something wrong. For example, in Objective-C data is stored using setters and is read using getters. Reading HTTP methods instead to make your invocation based service RESTish is mixing up two worlds into one. On the other hand a pattern is as good as its exceptions: There might be reasons to introspect the request or set the respond on the level of HTTP.

If your client application is developed in Objective-C, too, you should always use invocation based applications and never relay on the HTTP layer. With the client framework messages and invocation become transparent again.

Typing

The examples so far used instance objects of classes that are at least strongly related to JSON types. But Objective-Cloud offers you the full flavor of classes. This explicitly includes easy usage of classes that are related to JSON out of the box and custom classes defined by you.

As an Objective-C developer you are familiar with dynamic typing. Running code on a server, which is reachable for everybody, dynamic typing becomes a security problem: While wrong typing (like passing an instance of NSString, where a parameter is typed NSNumber *) inside your application leads to warnings that you can fix, even a bug free cloud application can receive values of wrong types. Therefore the type of every value has to be checked before it is used.

Because this will lead to a mass of boiler plate code, we automized checking in nearly all cases. But there are still two cases we need help:

  • For the type id every instance is of an allowed class. You have to check, whether it is an expected class.
  • Having a collections (NSArray, NSDictionary, …) as allowed type you have to check, whether the members of the collection are of the expected types. (This is the same situation as above, because members of collections are id typed, but it becomes a different shade in this context.)

Type system

Every value (instance object) and every type used in your source code has a base type. This is the type after removing all indirections. Some examples:

Type Base Type
id id
NSString* NSString
NSError** NSError
Class Class

Since scalar (C-ish) types like int, char, … are not allowed, the base type is always a class including id.

For some reasons it is helpful to distinguish between groups of types:

  • So called (native) JSON classes: NSNumber (JSON boolean and JSON number), NSString, NSArray, NSDictionary (JSON object)
  • Cocoa subclasses of JSON classes: NSMutableString, NSMutableArray, NSMutableDictionary
  • Cocoa collection classes related to JSON classes: NSSet, NSOrderedSet, NSIndexSet and the subclasses NSMutableSet, NSMutableOrderedSets, resp. NSMutableIndexSet.
  • Supported Cocoa classes: NSData, NSError, and NSURL provided by OCFoundation out of the box
  • Other classes including custom classes defined by you.

Only the last group of classes ("other classes") are not supported out of the box. You can add support for them very easily as described in the chapter "Custom parameter types".

The Objective-C runtime environment does not provide information about the exact type of an object reference parameter. All types are simply "object reference" (@). We retrieve the additional information from source code. Doing this job we developed a custom compiler using clang's Lib Tooling called Cloud Interface Exporter. We call this technology "A type hardened Objective-C" (@hoc). Therefore the described type transformation and checking are only available, if the cloud interface protocol is compiled with the cloud interface exporter as done by default for the generated service template you clone from our servers. For classes added by you, you have to add them to the Export Cloud Interface build phase.

Every Objective-C class corresponds to a JSON type:

Cocoa Class JSON Type Example
NSNumber boolean

number

true

1.23

NSString

NSMutableString

string "Cloud"
NSArray

NSMutableArray

array [ 1, "Cloud", yes ]
NSDictionary

NSMutableDictionary

object¹ { "firstname" : "Amin", "lastname" : "Negm" }
Other object² { "__com.objective-cloud.objectType" : "Person:NSObject", "firstname" : "Amin", "lastname" : "Negm" }

¹The object must not contain the key "com.objective-cloud.objectType". ² The object has to contain the key "com.objective-cloud.objectType" with the class inheritance as its value as described here.

Custom Parameter Types

As mentioned before it is possible to have parameters of a custom type like Person. Supporting a custom type boils down to make the type JSON-serializable. There are two basic approaches:

  • By a cloud interface protocol: This is a very convenient way to define the public interface for a custom class. It is best practice for entity types ("model classes"), e. g. classes which follow the key-value pattern.
  • By implementing a few coding methods which convert an object to JSON and back. This is a good, if your class does not follow the key-value pattern, for example classes like NSData. In this document these classes are called containers.

It is possible to encode an object using `NSCoding`. You can do this using one of the above methods or by typing your parameter to `NSData`. But there are two good reasons not to do so: 1. `NSCoding` describes the state of an object to be persisted, not the state of an object to be public. 2. `NSCoding` is proprietary to Cocoa.

Cloud Interface

The easiest way to convert an object to JSON and back is to define a cloud interface for the class of the object. You simply do this by enumerating the properties that should be transmitted:

@protocol PersonCloudInterface
@property NSString *name;
@property NSNumber *age;
@end

To link the protocol to the class, simply add it in its interface

@interface Person : NSObject <PersonCloudInterface>
@end

You can repeat the properties in the class interface to get automatic synthesization of accessors.

When the object is constructed from JSON, it receives messages in this order:

  • The class object gets a alloc message.
  • The instance object gets an init message.
  • The properties of the object are set by using its setters.

Coding Methods

In some cases it is inconvenient to declare the object's content with properties. You can use NSCoding-style JSON encoding for that. You have to implement to methods -encodeWithJSONEncoder:error: and +newWithJSONDecoder:error:.

- (BOOL)encodeWithJSONEncoder:(id<OCFJSONEncoder>)encoder error:(NSError **)error
{
    [encoder encodeObject: forKey:@"base64" error:error];
    return YES;
}

+ (id)newWithJSONDecoder:(id<OCFJSONDecoder>)decoder error:(NSError **)error
{
   NSString *base64 = [decoder decodeObjectOfClass:[NSString class] forKey:@"base64" error:error];
   if (base64==nil) {
      return nil;
   }
   NSData *data = [[NSData alloc] initWithBase64EncodedString:base64 options:0];
   return data;
}

How to use values of custom classes is described here.

Invocations

After developing a invocation base service you can invoke it's methods by using HTTP and JSON. The body of the request contains the invocation details (the selector to invoke and which arguments to pass).

Basic Format

Every invocation consists of three parts:

  • The name of the cloud application (has to be unique and the name is also used as a subdomain)
  • The name of the receiving class object (which is part of the path of the HTTP request)
  • The invocation details described in JSON as part of the HTTP request body.

When creating a cloud app via Objective-Cloud Connect you have to enter a unique name for your app. This name is used to form the URL for your app. A typically URL looks like https://yourapp.obcl.io.

The name of the class becomes the path of the URL. As usual in Objective-C a class name has to be unique within your cloud application.

The JSON body is a JSON object (dictionary) with two keys: selector and arguments. The selector denotes a string containing the message selector you want to invoke (it is passed, as is, to NSSelectorFromString()). The arguments is an array with the arguments of the message. The format of the JSON body is described in more detail below.

You can use curl to quickly create and execute HTTP requests. With curl a request for a cloud application with the name yourapp and a service class called Service looks like this:

curl -X POST https://yourapp.obcl.io/Service -d '{ "selector" : "sayHello", "arguments" : [] }'

Arguments

The JSON that forms the HTTP body has to contain an array for the key arguments. The arguments array contains a list of values passed to the method as arguments.

Arguments to pass

The above example for a public method
@protocol ServiceCloudInterface <NSObject>
+ (NSString*)concat:(NSString*)firstPart with:(NSString*)secondPart;
@end
will lead to a curl like this:
curl -X POST https://yourapp.obcl.io/Service -d '{ "selector" : "concat:with:", "arguments" : [@"Amin", @"Negm"] }'
If the message has no arguments, the array has to be an empty array.
@protocol ServiceCloudInterface <NSObject>
+ (NSString*)sayHello;
@end
curl -X POST https://YourApp.obcl.io/Service -d '{ "selector" : "sayHello", "arguments" : [] }'
If an argument is an out argument, it is simply omitted. Having a public method …
@protocol ServiceCloudInterface <NSObject>
+ (void)methodChangingArg:(inout NSMutableString*)changedArg outArg:(NSString**)outArg;
@end
… you only have to pass one argument, because the second one is an (pure) out parameter:
curl -X POST https://YourApp.obcl.io/Service -d '{ "selector" : "changedArg:outArg:", "arguments" : [@"Hello"] }'

You can pass values typed as JSON classes as usual. You will get the appropriate Cocoa class

JSON Value Example Cocoa Class
null value null nil, [NSNull null]¹
Boolean true true NSNumber with YES
Boolean false false NSNumber with NO
integer number 9811 NSNumber with integerValue
floating point number 98.11 NSNumber with doubleValue
string "9811" NSString, NSMutableString²
array [1,2] NSArray, NSMutableArray, NSOrderedSet, NSMutableOrderedSet, NSIndexSet, NSMutableIndexSet²
object {name: "Amin", age: 44} NSDictionary, NSMutableDictionary²

¹ null is translated into nil, if it is outside a collection otherwise [NSNull null]. ² The concrete class depends on the typing of the parameter. If it is id, an instance of the JSON class is created.

Custom Classes

If a parameter type is a custom class, it is transmitted as a dictionary with a key for every property. You should add a key "__com.objective-cloud.objectType" with a string value containing the class hierarchy as a string of the form "subclass:baseClass:superclass…".

This argument class is matched with the class of the parameter. It is possible to send an instance of a subclass to the method, even though the subclass is not known by the server.

Objective-C is a class based programming language. As a basic concept an instance of a derived class is an instance of the base class, too. Therefore you can pass instances of subclasses as arguments to a method, expecting a parameter of a base class.

In Objective-Cloud this concept is respected. Having a method …:

+ (Person*)returnPerson:(Person*)person
{
    return person;
}

… and a class Personwith a cloud interface …:

@protocol PersonCloudInterface <NSObject>
@property NSString* firstName;
@property NSString* lastName;
@end

… you can send a request that contains a subclass Employee of Person:

curl http://localhost:10000/Service \ 
     -X POST  
     -d '{"selector" : "returnPerson:", "arguments" : [{ "firstName" : "Amin", "lastName" : "Negm", "salery" : 80000, "__com.objective-cloud.objectType" : "Employee:Person:NSObject" }]}'

This works, even if Employee is not known on the server side. This makes it easy to store additional data on the client side inside one object instead of splitting it into two objects: one containing the cloud application's state, another one containing the client's state.

The Objective-Cloud runtime system remembers the state of the subclass. If it is returned back to the client, you will get a dictionary for that subclass containing the full state passed to the server.

Response

The response of an invocation consists of an optional return value and optional out arguments. If a request failed, it reports an error or exception.

Basic Format

The response is always a JSON object. It can contain four different key-value pairs:

  • returnValue: A return value if the invoked method has a return value (different to void) and successful ran.
  • outArguments: An array with out arguments, if the method has out arguments. [link]
  • error: An error, if the runtime system detected an error while invoking the method.
  • exception: An exception, if the invoked method threw one.

Both nil and [NSNull null] are transmitted as JSON null.

Return Value

The type of the return value depends on the class of the returned instance object.

You can return a subclass of the return value's class. In this case the whole state of the subclass' member is returned, the type string contains the subclass, and the client has to handle the subclass' additional key-value pairs.

Out Arguments

The out arguments array has one member for every out argument. The typing and coding of these arguments are handled as usual.

Error

If the content or format of a request is invalid then the response contains an error. When we say "error" then we talk about a JSON encoded NSError. The cloud interface contains a domain, a code and a userInfo dictionary.

$ curl -X POST http://localhost:10000/Service -d '{"selector" : "unknown", "arguments" : []}'
{
  "error" : {
    "code" : 1407,
    "domain" : "com.objective-cloud.foundation",
    "userInfo" : 
    {
      "NSLocalizedDescription" : "The selector of an invocation request is unknown."
    },
    "__com.objective-cloud.objectType" : "NSError:NSObject"
  }
}

Exception

If a request causes the invoked method to throw an exception, this exception is responded as a JSON object with the keys name(string), reason (string), and callStack(array of string)