Table of Contents Previous Next
Logo
Client-Side Slice-to-Objective-C Mapping : 18.12 Mapping for Operations
Copyright © 2003-2009 ZeroC, Inc.

18.12 Mapping for Operations

As we saw in Section 18.11, for each operation on an interface, the proxy protocol contains two corresponding methods with the same name as the operation. To invoke an operation, you call it via the proxy handle. For example, here is part of the definitions for our file system from Section 5.4:
["objc:prefix:FS"]
module Filesystem { 
    interface Node { 
        idempotent string name(); 
    }; 
    // ...
}; 
The proxy protocol for the Node interface looks as follows:
@protocol FSNodePrx <ICEObjectPrx>
(NSMutableString *) name;
(NSMutableString *) name:(ICEContext *)context;
@end;
The name method returns a value of type NSMutableString. Given a proxy to an object of type Node, the client can invoke the operation as follows:
id<EXNodePrx> node = ...;       // Initialize proxy
NSString *name = [node name];   // Get name via RPC
The name method sends the operation invocation to the server, waits until the operation is complete, and then unmarshals the return value and returns it to the caller.
Because the name method autoreleases the return value, it is safe to ignore the return value. For example, the following code contains no memory leak:
id<EXNodePrx> node = ...;       // Initialize proxy
[node name];                    // Useless, but no leak
If you ignore the return value, no memory leak occurs because the next time the enclosing autorelease pool is drained, the memory will be reclaimed.

18.12.1 Normal and idempotent Operations

You can add an idempotent qualifier to a Slice operation. As far as the corresponding proxy protocol methods are concerned, idempotent has no effect. For example, consider the following interface:
interface Example {
    string op1();
    idempotent  string op2();
    idempotent  void op3(string s);
};
The proxy protocol for this interface looks like this:1
@protocol EXOpsPrx <ICEObjectPrx>
(NSMutableString *) op1;
(NSMutableString *) op1:(ICEContext *)context;
(NSMutableString *) op2;
(NSMutableString *) op2:(ICEContext *)context;
(void) op3:(NSString *)s;
(void) op3:(NSString *)s context:(ICEContext *)context;
@end;
Because idempotent affects an aspect of call dispatch, not interface, it makes sense for the mapping to be unaffected by the idempotent keyword.

18.12.2 Passing Parameters

In-Parameters

The parameter passing rules for the Objective‑C mapping are very simple: value type parameters are passed by value and non-value type parameters are passed by pointer. Semantically, the two ways of passing parameters are identical: the Ice run time guarantees not to change the value of an in-parameter.
Here is an interface with operations that pass parameters of various types from client to server:
struct NumberAndString {
    int x;
    string str;
};

sequence<string> StringSeq;

dictionary<long, StringSeq> StringTable;

interface ClientToServer {
    void op1(int i, float f, bool b, string s);
    void op2(NumberAndString ns, StringSeq ss, StringTable st);
    void op3(ClientToServer* proxy);
};
The Slice compiler generates the following code for this definition:
@interface EXNumberAndString : NSObject <NSCopying>
// ...
@property(nonatomic, assign) ICEInt x;
@property(nonatomic, retain) NSString *str;
// ...
@end

typedef NSArray EXStringSeq;
typedef NSMutableArray EXMutableStringSeq;

typedef NSDictionary EXStringTable;
typedef NSMutableDictionary EXMutableStringTable;

@protocol EXClientToServerPrx <ICEObjectPrx>
(void) op1:(ICEInt)i f:(ICEFloat)f b:(BOOL)b s:(NSString *)s;
(void) op2:(EXNumberAndString *)ns ss:(EXStringSeq *)ss
            st:(NSDictionary *)st;
(void) op3:(id<EXClientToServerPrx>)proxy;
@end;
Given a proxy to a ClientToServer interface, the client code can pass parameters as in the following example:
id<EXClientToServerPrx> p = ...;           // Get proxy...

[p op1:42 f:3.14 b:YES s:@"Hello world!"]; // Pass literals

ICEInt i = 42;
ICEFloat f = 3.14;
BOOL b = YES;
NSString *s = @"Hello world!";

[p op1:i f:f b:b s:s];                     // Pass simple vars

EXNumberAndString *ns = [EXNumberAndString numberAndString:42
                                           str:@"The Answer"];
EXMutableStringSeq *ss = [ExMutableStringSeq array];
[ss addObject:@"Hello world!"];
EXStringTable *st = [EXMutableStringTable dictionary];
[ss setObject:ss forKey:[NSNumber numberWithInt:0]];

[p op2:ns ss:ss st:st];                    // Pass complex vars

[p op3:p];                                 // Pass proxy
You can pass either literals or variables to the various operations. The Ice run time simply marshals the value of the parameters to the server and leaves parameters otherwise untouched, so there are no memory-management issues to consider.
Note that the invocation of op3 is somewhat unusual: the caller passes the proxy it uses to invoke the operation to the operation as a parameter. While unusual, this is legal (and no memory management issues arise from doing this.)

Passing nil and NSNull

The Slice language supports the concept of null ("points nowhere") for only two of its types: proxies and classes. For either type, nil represents a null proxy or class. For other Slice types, such as strings, the concept of a null string simply does not apply. (There is no such thing as a null string, only the empty string.) However, strings, structures, sequences, and dictionaries are all passed by pointer, which raises the question of how the Objective‑C mapping deals with nil values.
As a convenience feature, the Objective‑C mapping permits passing of nil as a parameter for the following types:
• Proxies (nil sends a null proxy.)
• Classes (nil sends a null class instance.)
• Strings (nil sends an empty string.)
• Structures (nil sends a default-initialized structure.)
• Sequences (nil sends an empty sequence.)
• Dictionaries (nil sends an empty dictionary.)
It is impossible to add nil to an NSArray or NSDictionary, so the mapping follows the usual convention that an NSArray element or NSDictionary value that is conceptually nil is represented by NSNull. For example, to send a sequence of proxies, some of which are null proxies, you must insert NSNull values into the sequence.
As a convenience feature, if you have a sequence with elements of type string, structure, sequence, or dictionary, you can use NSNull as the element value. For elements that are NSNull, the Ice run time marshals an empty string, default-initialized structure, empty sequence, or empty dictionary to the receiver.
Similarly, for dictionaries with value type string, structure, sequence, or dictionary, you can use NSNull as the value to send the corresponding empty value (or default-initialized value, in the case of structures).

Out-Parameters

The Objective‑C mapping passes out-parameters by pointer (for value types) and by pointer-to-pointer (for non-value types). Here is the Slice definition from page 544 once more, modified to pass all parameters in the out direction:
struct NumberAndString {
    int x;
    string str;
};

sequence<string> StringSeq;

dictionary<long, StringSeq> StringTable;

interface ServerToClient {
    void op1(out int i, out float f, out bool b, out string s);
    void op2(out NumberAndString ns,
             out StringSeq ss,
             out StringTable st);
    void op3(out ClientToServer* proxy);
};
The Slice compiler generates the following code for this definition:
@protocol EXServerToClientPrx <ICEObjectPrx>
(void) op1:(ICEInt *)i f:(ICEFloat *)f b:(BOOL *)b
            s:(NSMutableString **)s;
(void) op2:(EXNumberAndString **)ns ss:(EXMutableStringSeq **)ss
            st:(EXMutableStringTable **)st;
(void) op3:(id<EXClientToServerPrx> *)proxy;
@end
Note that, for types that come in immutable and mutable variants (strings, sequences, and dictionaries), the corresponding out-parameter uses the mutable variant.
Given a proxy to a ServerToClient interface, the client code can pass parameters as in the following example:
id<EXServerToClientPrx> p = ...; // Get proxy...

ICEInt i;
ICEFloat f;
BOOL b;
NSMutableString *s;

[p op1:&i f:&f b:&b s:&s];
// i, f, b, and s contain updated values now

EXNumberAndString *ns;
EXStringSeq *ss;
EXStringTable *st;

[p op2:&ns ss:&ss st:&st];
// ns, ss, and st contain updated values now

[p op3:&p];
// p has changed now!
Again, there are no surprises in this code: the caller simply passes pointers to pointer variables to a method; once the operation completes, the values of those variables will have been set by the server.

Memory Management for Out-Parameters

When the Ice run time returns an out-parameter to the caller, it does not make any assumptions about the previous value of that parameter (if any). In other words, if you pass an initialized string as an out-parameter, the value you pass is simply discarded and the corresponding variable is assigned a new instance. As an example, consider the following operation:
void getString(out string s);
You could call this as follows:
NSMutableString *s = @"Hello";
[p getString:&s];
// s now points at the returned string.
All out-parameters are autoreleased by the Ice run time before they are returned. This is convenient because it does just the right thing with respect to memory management. For example, the following code does not leak memory:
NSMutableString *s = @"Hello";
[p getString:&s];
[p getString:&s]; // No leak here.
However, because the pointer value of out-parameters is simply assigned by the proxy method, you must be careful not to pass a variable as an out-parameter if that variable was not released or autoreleased:
NSMutableString *s = [[NSMutableString alloc] initWithString:
                                                    @"Hello"];
[p getString:&s]; // Bad news!
This code leaks the initial string because the proxy method assigns the passed pointer without calling release on it first. (In practice, this is rarely a problem because there is no need to initialize out-parameters and, if an out-parameter was initialized by being passed as an out-parameter to an operation earlier, its value will have been autoreleased by the proxy method already.)
It is worth having another look at the final call of the code example on page 548:
[p op3:&p];
Here, p is the proxy that is used to dispatch the call. That same variable p is also passed as an out-parameter to the call, meaning that the server will set its value. In general, passing the same parameter as both an input and output parameter is safe (with the caveat we just discussed).

Receiving Return Values

The Objective‑C mapping returns return values in much the same way as out-parameters: value types are returned by value, and non-value types are returned by pointer. As an example, consider the following operations:
struct NumberAndString {
    int x;
    string str;
};

interface Ops {
    int getInt();
    string getString();
    NumberAndString getNumberAndString();
};
The proxy protocol looks as follows:
@protocol EXOpsPrx <ICEObjectPrx>
(ICEInt) getInt;
(NSMutableString *) getString;
(EXNumberAndString *) getNumberAndString;
@end
Note that, for types with mutable and immutable variants (strings, sequences, and dictionaries), the formal return type is the mutable variant. As for out-parameters, anything returned by pointer is autoreleased by the Ice run time. This means that the following code works fine and does not contain memory management errors:
EXNumberAndString *ns = [NSNumberAndString numberAndString];
ns.x = [p getInt];
ns.str = [p getString]; // Autoreleased by getString,
                        // retained by ns.str.

[p getNumberAndString]; // No leak here.
The return value of getString is autoreleased by the proxy method but, during the assignment to the property str, the generated code calls retain, so the structure keeps the returned string alive in memory, as it should. Similarly, ignoring the return value from an invocation is safe because the returned value is autoreleased and will be reclaimed when the enclosing autorelease pool is drained.

Chained Invocations

Consider the following simple interface containing two operations, one to set a value and one to get it:
interface Name {
    string getName();
    void setName(string name);
};
Suppose we have two proxies to interfaces of type Name, p1 and p2, and chain invocations as follows:
[p2 setName:[p1 getName]]; // No leak here.
This works exactly as intended: the value returned by p1 is transferred to p2. There are no memory-management or exception safety issues with this code.

nil Out-Parameters and Return Values

If an out-parameter or return value is a proxy or class, and the operation returns a null proxy or class, the proxy method returns nil. If a proxy or class is returned as part of a sequence or dictionary, the corresponding entry is NSNull.
For strings, structures, sequences, and dictionaries, the Ice run-time never returns nil or NSNull (even if the server passed nil or NSNull as the value). Instead, the unmarshaling code always instantiates an empty string, empty sequence, or empty dictionary, and it always initializes structure members during unmarshaling, so structures that are returned from an operation invocation never contain a nil instance variable (except for proxy and class instance variables).

1
For brevity, we will not show the methods with the additional trailing context parameter for the remainder of this chapter. Of course, the compiler generates the additional methods regardless. See Section 32.12 for more information on contexts.

Table of Contents Previous Next
Logo