Implement swift API for C++ multi-type structure

Consider a C++ method that retrieve struct of native typed arguments like enum class, sub-structs, std::string, int, etc...

I'd like to create a swift API that return the same struct but in swift variables

for example :

class ErrorMessage {
 public:
  int status;
  std::string message;
};

class serverResponse {
 public:
  ErrorMessage error;
  std::string str_value;
  std::uint16_t int_val;
  std::time_t last_seen;
  EnumVal status;  
};

serverResponse getServerResponse(); 

So I'd like to convert it to the swift equivalent struct with native members

open class serverResponseSwift : NSObject {

    open class var error: ErrorMessage { get }

    open var str_value: String { get }

    open var int_val: UInt16 { get }

    open var status: EnumVal { get }
 };

I know that direct conversion is not yet possible so I need to use objective-C++ code as a mediator. So I've used a bridging header to include the converting method in objective-C++ which will look like this :

@interface Converter
- (serverResponseSwift) getServerStatusSwift;
@end

and the equivalent .mm file will implement the conversion function, but can I use the swift Class in objective-c in order to fill it up according to the CPP serverResponse ?

@implementation Converter
- (serverResponseSwift) getServerStatusSwift {
   serverResponse x = getServerResponse();  

    /// How do I create serverResponseSwift out of serverResponse
}

Thanks !

By the looks of it you are familiar with neither swift nor obj-c, so there'll be lots for you to cover. This should get you started:

// ----------
// ServerResponseObjc.h file
#import <Foundation/Foundation.h>

@class ServerResponseSwift;

@interface ServerResponseObjc: NSObject
- (ServerResponseSwift*)serverResponseSwift;
@end

// ----------
// ServerResponseObjc.mm file

#import "ServerResponseObjc.h"
#import <UIKit/UIKit.h> // or similar
#import "ModuleName-Swift.h" // "product module name" here with "-Swift.h" suffix
#import "ServerResponseCpp.h" // your c++ header

@implementation ServerResponseObjc
- (ServerResponseSwift*)serverResponseSwift {
    serverResponse* r = new serverResponse();
    // TODO: check for no error
    NSString* errorString = [[NSString alloc] initWithUTF8String:r->error.message.c_str()];
    NSDictionary* userInfo = @{NSLocalizedDescriptionKey: errorString};
    NSError* error = [[NSError alloc] initWithDomain:@"Error" code:r->error.status userInfo:userInfo];
    NSString* str = [[NSString alloc] initWithUTF8String:r->str_value.c_str()];
    NSDate* lastSeen = [NSDate dateWithTimeIntervalSince1970:r->last_seen];
    int status = 0; // TODO: status
    return [[ServerResponseSwift alloc] initWithError:error str_value:str int_val:r->int_val last_seen:lastSeen status:status];
}
@end

// ----------
// ServerResponseSwift.swift file

import Foundation

class ServerResponseSwift: NSObject {
    @objc init(error: NSError, str_value: String, int_val: UInt16, last_seen: Date, status: Int) {
        self.error = error
        self.str_value = str_value
        self.int_val = int_val
        self.last_seen = last_seen
        self.status = status
        super.init()
    }
    var error: NSError
    var str_value: String
    var int_val: UInt16
    var last_seen: Date
    var status: Int // TODO
};

A few notes:

  • you can model your ErrorMessage with NSError
  • you can use NS_ENUM with your EnumVal (that you didn't show)
  • you have to return a pointer to "ServerResponseSwift" from Obj-C and properly allocate it in there
  • instead of using init with parameters (as shown above) you can have an empty init + set individual fields.
  • you need to put @objc before members you want to be accessible from Obj-C
  • search for "Product Module Name" in Xcode settings. You can either change it in there or change it in the source file, the two must be the same.

Hi and thanks for the comprehensive solution.

From why I read... In swift it's better dealing with errors using exceptions, and not by passing the error reference variable. However, if I throw an exception in the objective-c or the cpp part (for example, in method - (ServerResponseSwift*)serverResponseSwift { , than it wouldn't be supported in the swift part..

So I thought about adding method to fetch the data from swift (besides @objc init) and use it every time I want to fetch some data... But perhaps there's a better way (I know that in init we cannot use exceptions) ?

   getData() {
        self.error = error
        // here I'll throw an exception if the error is set
        ...    
    }

Swift Error values are not exceptions, and it's important to drop whatever conceptions of error handling you have with them right away when using Swift or Obj-C. Using an error parameter in Obj-C will be automatically translated into throwing function in Swift (makes with throws) as long as it's properly structured.

@Jon_Shier . Hi and thanks for the comment. regarding your statement

Using an error parameter in Obj-C will be automatically translated into throwing function in Swift (makes with throws) as long as it's properly structured.

Perhaps you can give me minimal example so I can understand how it works ? I assume that if the signature of the objc method include (NSError *) as last parameter, it's parsed by the swift context and converted into exception if error != nil. right ?

@tera, I followed you guidelines, and it works great ! thanks.

Perhaps you can just add some usage example of how to call this API from swift code.
For example what i did, but i don't know how to handle the error - I'd like to get an exception if the object from type serverResponseSwift has valid error field

            let obj = ServerResponseObjc().serverResponseSwift()
            let dateFormatter = DateFormatter()
            self.textField = "address = " + obj!.address_
            self.textField += "\nport = " + "\(obj!.port_)"

As @Jon_Shier rightly mentioned swift error handling is not called exceptions: even if it uses similar try and catch keywords the mechanism is totally different. This shows a few examples: Handling Cocoa Errors in Swift.

Your obj-c serverResponseSwift needs to return BOOL and have NSError** as the last parameter, and in swift you'll call it with try try? or try! as in the examples.

Using and Creating Error Objects.

The thing is that I would rather return the swift object and not BOOL so that i can use it later on the code.
The NSError** part is hidden in swift so i don't mind adding it.

is there an option that I can write the objective-c signature with NSError** but returning the ServerResponseSwift object and still get the exception

In objc code :

@interface ServerResponseObjc
- (ServerResponseSwift*)serverResponseSwiftWithError:(NSError **)error;
@end

and use in swift like this :

do {
    let obj = ServerResponseObjc().serverResponseSwift()
} catch let error as NSError {
    print(error)
}

is it supported ?

Try this, it shall also work:

- (ServerResponseSwift*)serverStatusSwift:(NSError**)error;

and consistently return non nil when error == nil and return nil when error <> nil.

on swift side you'd call it like:

do {
    let result = try serverStatusSwift()
    // use result here
} catch {
    print(error)
}

or one of these:

let result = try? serverStatusSwift()
let result = try! serverStatusSwift()

Thanks, that exactly what I needed !