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.
id<EXNodePrx> node = ...; // Initialize proxy
[node name]; // Useless, but no leak
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);
};
@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.
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.
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);
};
@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:
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).
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 588 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);
};
@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.
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);
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.)
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).
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();
};
@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.
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).