Swift Mapping for Operations
On this page:
Basic Swift Mapping for Operations
As we saw in the mapping for interfaces, for each operation on a Slice interface, the corresponding proxy protocol extension contains a method with the same name. To invoke an operation, you call it via the proxy. For example, here is part of the definitions for our file system:
module Filesystem { interface Node { idempotent string name(); } // ... }
The name
operation returns a value of type string
. Given a proxy to an object of type Node
, the client can invoke the operation as follows:
let node: NodePrx = ... // Initialize proxy let name = try node.name() // Get name via RPC
Normal and idempotent
Operations in Swift
You can add an idempotent
qualifier to a Slice operation. As far as the signature for the corresponding proxy method is concerned, idempotent
has no effect. For example, consider the following interface:
interface Example { string op1(); idempotent string op2(); }
The proxy interface for this is:
public protocol ExamplePrx: ObjectPrx {} public extension ExamplePrx { func op1(context: Ice.Context? = nil) throws -> String { // ... } func op2(context: Ice.Context? = nil) throws -> String { // ... } // ... }
Because idempotent
affects an aspect of the operation invocation, not interface, it makes sense for the two operations to be mapped the same.
Passing Parameters in Swift
In Parameters
The parameter passing rules for the Swift mapping are very simple: each in
parameter of a Slice operation is mapped to a corresponding parameter in the mapped Swift method.
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 proxy for these definitions:
public struct NumberAndString: Hashable { public var x: Int32 = 0 public var str: String = "" // ... } public typealias StringSeq = [String] public typealias StringTable = [Int64: StringSeq] public protocol ClientToServerPrx: Ice.ObjectPrx {} public extension ClientToServerPrx { func op1(i: Int32, f: Float, b: Bool, s: String, context: Ice.Context? = nil) throws { // ... } func op2(ns: NumberAndString, ss: StringSeq, st: StringTable, context: Ice.Context? = nil) throws { // ... } func op3(_ proxy: ClientToServerPrx?, context: Ice.Context? = nil) throws { // ... } // ... }
Given a proxy to a ClientToServer
interface, the client code can pass parameters as in the following example:
let p: ClientToServerPrx = ... // Get proxy... try p.op1(i: 42, f: 3.14, b: true, s: "Hello world!") // Pass simple literals let i: Int32 = 42 let f: Float = 3.14 let b: Bool = true let s: String = "Hello world!" try p.op1(i: i, f: f, b: b, s: s) // Pass simple variables var ns = NumberAndString() ns.x = 42 ns.str = "The Answer" let ss = ["Hello world!"] var st = [0: ss] try p.op2(ns: ns, ss: ss, st: st) // Pass complex variables try p.op3(p) // Pass proxy
When an operation has a single input parameter the proxy generated method doesn't use a label for the input parameter, for methods that have more than one input parameter each input parameter uses a label that corresponds to the name of the parameter in the Slice definition.
Out Parameters
The mapping for an operation depends on how many values it returns, including out parameters and a non-void
return value:
- Zero values
The corresponding Swift method returnsvoid
. For the purposes of this discussion, we're not interested in these operations.
One value
The corresponding Swift method returns the mapped type, regardless of whether the Slice definition of the operation declared it as a return value or as an out parameter. Consider this example:Sliceinterface I { string op1(); void op2(out string name); }
The mapping generates corresponding methods with identical signatures:
Swiftpublic protocol IPrx: Ice.ObjectPrx {} public extension IPrx { func op1(context: Ice.Context? = nil) throws -> String { // ... } func op2(context: Ice.Context? = nil) throws -> String { // ... } // ... }
- Multiple values
The mapped Swift method returns a tuple. If the operation declares a return value, this value is provided as the first element of the tuple with the labelreturnValue
. If an out parameter is also namedreturnValue
, the label for the operation's return value is named_returnValue
instead.
Here are the same Slice definitions we saw earlier, but this time with all parameters being passed in the out
direction, along with one additional operation to better demonstrate the mapping:
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 ServerToClient* proxy); StringSeq op4(out string returnValue); }
The Slice compiler generates the following code for these definitions:
public protocol ServerToClientPrx: Ice.ObjectPrx {} public extension ServerToClientPrx { func op1(context: Ice.Context? = nil) throws -> (i: Int32, f: Float, b: Bool, s: String) { // ... } func op2(context: Ice.Context? = nil) throws -> (ns: NumberAndString, ss: StringSeq, st: StringTable) { // ... } func op3(context: Ice.Context? = nil) throws -> ServerToClientPrx? { // ... } func op4(context: Ice.Context? = nil) throws -> (_returnValue: StringSeq, returnValue: String) { // ... } }
Optional Parameters
An optional parameter is mapped to a Swift parameter with the corresponding Swift optional type. Consider the following operation:
optional(1) int execute(optional(2) string params);
The mapping for this operation is shown below:
Int32? execute(params: String? = nil, context: Ice.Context? = nil)
The generated code uses nil default value for optional parameters, omitting a default parameter will result in a unset optional.
The Swift mapping does not allow you to represent an optional proxy or class parameter that holds a null
value. Consider this example:
class Data { ... } interface Repository { void addOptional(optional(1) Data d); void addRequired(Data d); }
The Ice encoding allows null
values for class instances so with some language mappings you can pass null
to addRequired
and the server will receive it as null
. There is however no way to do this with the Swift mapping: passing nil
to addOptional
means passing an optional that is not set.
Exception Handling in Swift
Any operation invocation may throw a run-time exception and, if the operation has an exception specification, may also throw user exceptions. Suppose we have the following simple interface:
exception Tantrum { string reason; } interface Child { void askToCleanUp() throws Tantrum; }
Slice exceptions are thrown as Swift exceptions, so you can simply enclose one or more operation invocations in a do-catch
block:
let child: ChildPrx = ... // Get child proxy... do { try child.askToCleanUp() } catch let t as Tantrum { print("The child says: " + t.reason) }
Typically, you will catch only a few exceptions of specific interest around an operation invocation; other exceptions, such as unexpected run-time errors, will typically be handled by exception handlers higher in the hierarchy. For example:
func main() { do { ChildPrx child = ... // Get child proxy... do { try child.askToCleanUp() try child.praise() // Give positive feedback... } catch let t as Tantrum { print("The child says: \(t.reason)") try child.scold() // Recover from error... } exit(0) } catch { print(error) exit(1) } }