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 !
tera
2
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
...
}
Jon_Shier
(Jon Shier)
4
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_)"
tera
8
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 ?
tera
11
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 !