Objective-C in the Cloud

Invocation-based Services

Introduction

Invocation-based services are simple to implement actions run on Objective-Cloud's servers. They can be used for both designing a service for an app client or a browser client.

For selecting the right type of service refer here.

Before you continue

You should have read the Objective-Cloud tutorial and worked through the steps described there.

Please start terrasphere, create and clone a new cloud application for this tutorial as described here, open it in Xcode and launch it by simply building it.

Overview

Invocation-based services provide a message based API to a service. As in a local environment this is provided by a class with a public API declared by an Objective-C protocol and an implementation that defines the methods reacting on messages. While the protocol is exported to the client, the implementation is hidden on the server.

A public service consists of three areas that can be understood as ranges of publicity:

  • The cloud interface protocol contains the public interface to the client.
  • The class interface that publishes methods inside the service, but not to the client.
  • The class implementation that implements the methods declared in the cloud interface and the class interface.

You can pass arguments to the service and receive return values.

Sending Invocations

In this tutorial deals with invocation-based cloud application. Such a service is useful with a client counterpart that interacts with the service. There are several ways to send messages to the service. For this tutorial we use GET requests with URL encoded arguments. The default response you will read is in JSON. We selected this format for messaging, because we can provide links that does the work for you, so you do not have to type it yourself. Even this is an implementation detail for the service, you should have an idea of what is going on behind the scenes, last but not least for doing your own experiments:

Independently of the format invocation request contains three parts:

  • the receiver
  • the selector of the message
  • the arguments of the message

As mentioned we will send GET requests using our browser. The general format is:

http://localhost:10000/service/selector?[?parameter=argument{&parameter=argument}]

For example the Objective-C message …


+ (NSString*)sayHelloWithFirstName:(NSString*)firstName lastName:(NSString*)lastName;
…
[Service sayHelloWithFirstName:@"Amin" lastName:@"Negm-Awad"];

looks like this, when it is a GET request:
http://localhost:10000/Service/sayHelloWithFirstName-lastName-?firstName=Amin&lastName=Negm-Awad
Please note that the colons of the selector are replaced by with `-` (minus). However, Objective-Cloud accepts colons inside the selector and rfc3986, section-3.3 only forbids it for scheme less path. You get the parameter names from the parameter variables in the cloud interface. The response you will get in this tutorial is a JSON document. But at the end we will make it be HTML. By default every cloud application contains a simple service class named `Service` with a single method `+publicVersion`, inherited from `OCFPublicObject` taking no arguments. After starting terrasphere and the cloud application, you can check that. Sending a GET request for the Objective-C message …:
[Service publicVersion]
… you should get the result:
{
  "returnValue" : 0
}
Try me using your browser. Please note the URL.

Providing a Service Class

An invocation-based service is provided by a class derived from `OCFPublicObject`. You can have as many service classes inside a cloud application as you want. By default there is already one class called `Service` with an appropriate cloud interface file in the template. In this tutorial we will use this one.

Adding a public Method

Adding a public method is quite simple: You declare it in the cloud interface protocol acting as public interface and define it in the class implementation.

Adding the Definition

A method definition is added to the class implementation as usual. Browse to Service.m and add a simple method (A).
@implementation Service

+ (NSString *)sayHelloTo:(NSString*)name                // A
{
  return [@"Hello " stringByAppendingString:name];
}
@end
As you can see, there is nothing special about the method. It has a usual name, can take arguments and return values as you expected.

Publishing the Method

If you build and run the changed application now and send a message to it, you would get an error
[Service sayHelloTo:@"Amin"]
Try me using your browser.
{
  "error" : {
    "code" : 1407,
    "domain" : "com.objective-cloud.foundation",
    "userInfo" : {
      "NSLocalizedDescription" : "The selector of an invocation request is unknown."
    }
    "__com.objective-cloud.objectType" : "NSError:NSObject",
  }
}
As usually in Objective-C purely defining a method inside an implementation makes it private to the implementation. Adding it to the class interface wouldn't help: This makes the method public to the parts of the cloud application importing Service.h, but not to the client. We need a additional level of publicity which is provided by a protocol called the cloud interface. It always has the suffix `CloudInterface`. If the protocol simply describes to API of a class, its name is Class`CloudInterface`.
A class can have additional cloud interfaces beside its own one, called the primary cloud interface. This offers you the capability to define cloud interfaces that are incorporated in different classes like protocols in Objective-C.
With Xcode's project navigator browse to the file *ServiceCloudInterface.h* and add a method declaration to the protocol (A):
@protocol ServiceCloudInterface <NSObject>
+ (NSString *)sayHello;
+ (NSString *)sayHelloTo:(NSString*)name; // A
@end
From now you can use that method remotely.
[Service sayHelloTo:@"Amin"]
{
  "returnValue" : "Hello Amin"
}
Try me using your browser.

Arguments and Parameters

As you could see in the above examples, it is possible to pass arguments inside the message and retrieve a return value as a reaction. Parameters are automatically set to the arguments. You do not have to access it with key-value coding et al. from the request, even this is possible.

Supported Classes

The parameters has to be typed to object references. For numbers (including booleans) that means, that you have to do a typing on `NSNumber *`. You can use boxed literals and expressions. Beside the limitation that all values has to be objects, you can use any JSON type (booleans, numbers, strings, arrays and dictionaries, in JSON called objects) as argument or return value out of the box. They are automatically translated to the corresponding Cocoa classes as `NSJSONSerializer` does. Moreover if a parameter is typed to a class that is related to a JSON class, the JSON value is automatically translated into the parameter's class. Let's have an example: Add a new method to the implementation of `Service` …:
@implementation Service

+ (NSArray *)addItem:(NSString *)item toList:(NSMutableArray*)list
{
  [list addObject:item];
  return list;
}
@end
… and publish it in ServiceCloudInterface.h:
@protocol ServiceCloudInterface <NSObject>

+ (NSArray *)addItem:(NSString *)item toList:(NSMutableArray*)list;
@end
In contrast to `NSJSONSerializer`, which would create a instance object of `NSArray` by default, you will get an instance object of `NSMutableArray`, because this is the type of the parameter.
But how can one pass collections using URL-encoding? Quite easy: The marshaller for invocation-based applications knows dictionaries and arrays and automatically glues them into a complex object graph if needed. This is done recursively, i.e …:
group[name]=Developers&group[members][0][firstName]=Amin&group[members][0][lastName]=Negm&group[members][1][firstName]=Chris&group[members][1][lastName]=Kienle
… creates an argument for the parameter `group` being a dictionary:
@{
   @"name"    : @"Developers",
   @"members" : 
      @[                            // Array
         @{                  
            @"firstName" : @"Amin", // Dictionary
            @"lastName"  : @"Negm"
         },
         @{                  
            @"firstName" : @"Chris", // Dictionary
            @"lastName"  : @"Kienle"
         }
      ]
   }
@}
Let's try it for the new method:
[Service addItem:@"Chris" toList:@[@"Amin"]] // Passed as mutable array
{
  "returnValue" : [
    "Amin",
    "Chris"
  ]
}
Try me using your browser. This rule applies to all mutable subclasses of JSON classes (`NSMutableString`, `NSMutableDictionary`, `NSMutableArray`) and – to make goods things better – to classes that are related to JSON classes like `NSSet`, `NSOrderedSet`, and `NSIndexSet` and their mutable subclasses. Using "untyped"URL-encoding strings containing numbers are translated to instances of `NSNumber`, if the parameter is typed that way. If you simply change the typing of the second parameter and the return value to `NSMutableSet` resp. `NSSet` …:
@protocol ServiceCloudInterface <NSObject>

+ (NSSet *)addItem:(NSString *)item toList:(NSMutableSet*)list;
@end
@implementation Service

+ (NSSet *)addItem:(NSString *)item toList:(NSMutableSet*)list
{
  [list addObject:string];
  return list;
}
@end
… Objective-Cloud still does its work:
[Service addItem:@"Chris" toList:@[@"Amin"] // Passed as mutable set
{
  "returnValue" : [
    "Chris",
    "Amin"
  ]
}
Try me using your browser. Please note, that the order of the items inside the collection can change – because the code uses sets. To make better things best, it is even possible to pass instance objects of your custom class to the method. To get more information about supported classes refer to [Invocation-based Services in Detail](/docs/invocation-based-services-in-detail).

Out Parameters

Sometimes a parameter is not used to pass an argument (only), but to receive a result. There are to patterns: * An indirect object reference like the Cocoa error handling pattern with `NSError **` * Pass of a mutable object changed inside the method
Changing mutable arguments inside a method looks like a code smell. But there can be good reasons to do so. However, for stateless services that are not backed with a database, it is a good pattern to change collections as shown below.
First we add Cocoa's error pattern to the method. The argument for the `list` parameter should not be `nil`:
@protocol ServiceCloudInterface <NSObject>

+ (NSSet *)addItem:(NSString *)item toList:(NSMutableSet*)list error:(NSError **)error;
@end
@implementation Service

+ (NSSet *)addItem:(NSString *)item toList:(NSMutableSet*)list error:(NSError**)error
{
  if (list==nil)
  {
    *error = [NSError errorWithDomain:@"yourDomain" code:1 userInfo:nil];
    return nil;
  }
  [list addObject:item];
  return list;
}
@end
Sending a message with a `nil` for the list parameter (in a query string simply omit the argument) now is producing a `nil` return value and an error as it should do:
http://localhost:10000/Service/addItem-toList-?item=Chris
http://localhost:10000/Service/addItem-toList-?item=Chris&list[0]=Amin
{
  "outArguments" : [
    {
      "code" : 1,
      "domain" : "yourDomain",
      "userInfo" : {

      },
      "__com.objective-cloud.objectType" : "NSError:NSObject"
    }
  ],
  "returnValue" : null
}
As you can see, a response can contain both out arguments and a return value. Moreover it can contain more than one out argument and different kind of out arguments. Please focus on the curl sent: It does not contain an argument for the `error` parameter. Indirect parameters are assumed to be an out parameter only. To out parameters you cannot pass an argument.
You can change this behavior with the Objective-C keyword `inout` as shown below for direct out parameters. For indirect parameters there should be little reason to do so.
Looking to the last example, one might ask, why the list is returned, instead of directly using the list argument. We can change that easily with an out parameter. You simply have to add the Objective-C keyword `inout`. Use `NSNumber *` as return type to flag an error according to the Cocoa error pattern:
@protocol ServiceCloudInterface <NSObject>

+ (NSNumber *)addItem:(NSString *)item toList:(inout NSMutableSet*)list error:(NSError **)error;
@end
@implementation Service

+ (NSNumber *)addItem:(NSString *)item toList:(inout NSMutableSet*)list error:(NSError**)error
{
  if (list==nil)
  {
    *error = [NSError errorWithDomain:@"yourDomain" code:1 userInfo:nil];
    return @NO;
  }
  [list addObject:string];
  return @YES;
}
@end
With this change the resulting list is returned as out argument:
http://localhost:10000/Service/addItem-toList-error-?item=Chris&list[0]=Amin
{
  "outArguments" : [
    [
      "Chris",
      "Amin"
    ],
    null
  ],
  "returnValue" : true
}

Type checking

Objective-C has a dynamic type system: The type of an object is determined at runtime. This makes it possible to use instance objects of wrong classes when sending a message. In a local application the sender knows the API of the receiver from the receiver's interface. Having a mismatch between the type of an argument and the parameter's type will lead to a warning generated by the compiler. You would fix that. In contrast with a cloud application the compiler translating the source code of the client may not know the service's interface. So you will get no warnings passing the wrong arguments in a request.
If the client uses the Objective-Cloud client framework for Objective-C, stand-in classes are used for each service class. This makes it possible to clang to generate warnings.
Even worse, if you did everything correct, someone may want to attack your service by sending corrupted requests. This vector typically leads to boilerplate code at the beginning of a request handler to check the arguments one by one. This is the bad news. The good news is that Objective-Cloud handles type checking for invocation-based services automatically. I. e. in the above example `-addItem:toList:` is not executed at all, when the request contains a dictionary instead of the expected string as first argument:
http://localhost:10000/Service/addItem-toList-error-?item[firstName]=Chris&item[lastName]=Kienle&list[0]=Amin
{
  "error" : {
    "code" : 1002,
    "domain" : "com.Objective-Cloud.JSONCoding",
    "userInfo" : {
      "expectedClassName" : "NSString",
      "NSLocalizedDescription" : "Incompatible types of value. Expected NSString, found __NSDictionaryM.",
      "foundClassName" : "__NSDictionaryM"
    },
    "__com.objective-cloud.objectType" : "NSError:NSObject"
  }
}
This works for custom classes, too. But sometimes one wants to take advantage out of the dynamic type system of Objective-C. This is still possible, if the parameter type is `id`. The above method is a good example for an usage of `id`:
  
@protocol ServiceCloudInterface 
…
+ (NSNumber *)addItem:(id)item toList:(inout NSMutableSet*)list error:(NSError **)error;
@end
  
@implementation Service
…
+ (NSNumber *)addItem:(id)item toList:(inout NSMutableSet*)list error:(NSError**)error
{
  if (list==nil)
  {
    *error = [NSError errorWithDomain:@"yourDomain" code:1 userInfo:nil];
    return @NO;
  }
  [list addObject:string];
  return @YES;
}
@end

Now we can add a dictionary instance to the list:

http://localhost:10000/Service/addItem-toList-error-?item[firstName]=Chris&item[lastName]=Kienle&list[0]=Amin

curl -X POST http://localhost:10000/Service -d '{"selector" : "addItem:toList:error:", "arguments" : [2,["Amin"]]}';2D;2D;2D;2D
{
  "outArguments" : [
    [
      {
        "firstName" : "Chris",
        "lastName"  : "Kienle"
      },
      "Amin"
    ],
    null
  ],
  "returnValue" : true
}

Further Reading

There are many additional capabilities of invocation-based services, i. e.

  • returning values asynchronously
  • returning non-JSON reponses like HTML
  • passing instance objects of custom classes to a method or returning them

To get information about this in detail, please refer to Invocation-based services in Detail