The server-side mapping for interfaces provides an up-call API for the Ice run time: by implementing methods in a servant class, you provide the hook that gets the thread of control from the Ice server-side run time into your application code.
On the client side, interfaces map to proxy protocols and classes (see Section 18.11). On the server side, interfaces map to
skeleton protocols and classes. A skeleton is a class that has a method for each operation on the corresponding interface. For example, consider the Slice definition for the
Node interface we defined in
Chapter 5 once more:
["objc:prefix:FS"]
module Filesystem {
interface Node {
idempotent string name();
};
// ...
};
@protocol FSNode <ICEObject>
‑(NSString *) name:(ICECurrent *)current;
@end
@interface FSNode : ICEObject
// ...
@end
As you can see, the server-side API consists of a protocol and a class, known as the
skeleton protocol and
skeleton class. The methods of the skeleton class are internal to the mapping, so they do not concern us here. The skeleton protocol defines one method for each Slice operation. As for the client-side mapping, the method name is the same as the name of the corresponding Slice operation. If the Slice operation has parameters or a return value, these are reflected in the generated method, just as they are for the client-side mapping. In addition, each method has an additional trailing parameter of type
ICECurrent. This parameter provides additional information about an invocation to your server-side code (see
Section 32.6).
As for the client-side mapping, the generated code reflects the fact that all Slice interfaces and classes ultimately derive from
Ice::Object. As you can see, the generated protocol incorporates the
ICEObject protocol, and the generated class derives from the
ICEObject class.
The Objective‑C mapping supports two different ways to implement servants. You can implement a servant by deriving from the skeleton class and implementing the methods for the Slice operations in your derived class. Alternatively, you can use a delegate servant, which need not derive from the skeleton class.
To provide an implementation for an Ice object, you can create a servant class that derives from the corresponding skeleton class. For example, to create a servant for the
Node interface, you could write:
@interface NodeI : FSNode <FSNode>
{
@private
NSString *myName;
}
+(id) nodei:(NSString *)name;
@end
By convention, servant classes have the name of their interface with an I‑suffix, so the servant class for the
Node interface is called
NodeI. (This is a convention only: as far as the Ice run time is concerned, you can chose any name you prefer for your servant classes.)
Note that NodeI derives from
FSNode, that is, it derives from its skeleton class. In addition, it adopts the
FSNode protocol. Adopting the protocol is not strictly necessary; however, if you do write your servants this way, the compiler emits a warning if you forget to implement one or more Slice operations for the corresponding interface, so we suggest that you make it a habit to always have your servant class adopt its skeleton protocol.
As far as Ice is concerned, the NodeI class must implement the single
name method that is defined by its skeleton protocol. That way, the Ice run time gets a servant that can respond to the operation that is defined by its Slice interface. You can add other methods and instance variables as you see fit to support your implementation. For example, in the preceding definition, we added a
myName instance variable and property, a convenience constructor, and
dealloc. Not surprisingly, the convenience constructor initializes the
myName instance variable, the
name method returns the value of that variable, and
dealloc releases it:
@implementation NodeI
+(id) nodei:(NSString *)name
{
NodeI *instance = [[[NodeI alloc] init] autorelease];
instance.myName = [[name copy] retain];
return instance;
}
‑(NSString *) name:(ICECurrent *)current
{
return myName;
}
‑(void) dealloc
{
[myName release];
[super dealloc];
}
@end
@interface ICEObject NSObject <ICEObject, NSCopying>
// ...
‑(id) initWithDelegate:(id)delegate;
+(id) objectWithDelegate:(id)delegate;
@end
The delegate parameter specifies an object to which the servant will delegate operation invocations. That object need not derive from the skeleton class; the only requirement on the delegate is that it must have an implementation of the methods corresponding to the Slice operations that are called by clients. As for derived servants, we suggest that the delegate adopt the skeleton protocol, so the compiler will emit a warning if you forget to implement one or more Slice operations in the delegate.
Delegate servants are useful if you need to derive your servant implementation from a base class in order to access some functionality. In that case, you cannot also derive the servant from the generated skeleton class. A delegate servant gets around Objective‑C’s single inheritance limitation and saves you having to write a servant class that forwards each operation invocation to the delegate.
interface Intf1 {
void op1();
};
interface Intf2 {
void op2();
};
If op1 and
op2 are substantially similar in their implementation and share common state, it can be convenient to implement the servants for
Intf1 and
Intf2 using a common delegate class:
@interface Intf1AndIntf2 : NSObject<EXIntf1, EXIntf2>
+(id) intf1AndIntf2;
@end
@implementation Intf1AndIntf2
+(id) intf1AndIntf2 { /*...*/ }
‑(void) op1:(ICECurrent*)current { /*...*/ }
‑(void) op2:(ICECurrent*)current { /*...*/ }
@end
See page 636 for an example of how to instantiate delegate servants.
Delegate servants do not permit you to override operations that are inherited from
ICEObject (such as
ice_ping). Therefore, if you want to override
ice_ping, for example, to implement a default servant (see
page 966), you must use a derived servant.