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:

Slice
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:

Swift
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:

Slice
interface Example
{
    string op1();
    idempotent string op2();
}

The proxy interface for this is:

Swift
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:

Slice
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:

Swift
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:

Swift
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 returns void. 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:

    Slice
    interface I
    {
        string op1();
        void op2(out string name);
    }

    The mapping generates corresponding methods with identical signatures:

    Swift
    public 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 label returnValue. If an out parameter is also named returnValue, 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:

Slice
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:

Swift
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:

Slice
optional(1) int execute(optional(2) string params);

The mapping for this operation is shown below:

Swift
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:

Slice
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:

Slice
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:

Swift
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:

Swift
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)
    }
}


See Also