3

I have some Objective-C++ code like this:

// header

@interface MyObjcClass

- (void) myMethod: (NSString *) text;

@end


// implementation

@implementation MyObjcClass

- (void) myMethod: (NSString *) text
{
    someInternalObject->useWstring(nsStringToWstring(text))
}

std::wstring nsStringToWstring(const NSString * text)
{
    NSData * data = [text dataUsingEncoding: NSUTF32LittleEndianStringEncoding];

    // and then some other stuff irrelevant to this question

    return std::wstring(pointerToNewString, pointerToNewString + stringLength);
}

@end

Straightforward so far; seems fine to me. And works great when called from Objective-C and Objective-C++ code! However, when Swift gets involved...

func mySwiftFunc(text: String) {
    myObjcClassInstance.myMethod(text)
}

This also seems straightforward. The compiler's translation layer automatically bridge's Swift's String to Objective-C++'s NSString *. Unfortunately, that's false... it seems to bridge it to something called _SwiftValue. And then that's passed in and I try to call dataUsingEncoding: on it, but apparently _SwiftValue doesn't have such a selector, so I get this crash:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_SwiftValue dataUsingEncoding:]: unrecognized selector sent to instance 0x7f9ca7d63ee0'

I'm at a loss for how to resolve this. I can't use NSString in Swift and just pass that down because the compiler requires that I pass a Swift String.

Is there any way to resolve this?

Any of these solutions will do:

  • Force generated Swift header to accept a NSString * instead of a String
  • A way to turn a _SwiftValue into data from within Objective-C++
  • Any flags that let the compiler or runtime know to translate that _SwiftValue to a NSString * before trying to message it
  • etc.

Environment

  • The ObjC++ code is within a modular framework that is referenced by the Swift code; they're not compiled together.
  • The ObjC++ module is compiled with Xcode 8.3.3 and the Swift code with 9.2.
  • There may be other discrepancies; there are lots of moving parts in these projects.
Ky -
  • 30,724
  • 51
  • 192
  • 308
  • 1
    Another option (have not tried myself) - adding pure objective-c class as an extra layer between swift and c++ that would handle type conversions – Vladimir Nov 26 '18 at 21:04
  • I cannot reproduce the issue, but of course I may be overlooking something. – Martin R Nov 26 '18 at 21:20
  • @MartinR This may be due to several reasons... the ObjC++ code is within a modular framework that is referenced by the Swift code; they're not compiled together. Additionally, the ObjC++ module is compiled with Xcode 8.3.3 and the Swift code with 9.2. I'm sure there are other discrepancies; there are lots of moving parts in these projects. – Ky - Nov 26 '18 at 21:34
  • 1
    That would be useful information in the question ... – Martin R Nov 26 '18 at 21:36
  • @MartinR Added! – Ky - Nov 26 '18 at 21:46
  • Swift Strings are not NSString and so don't respond to messages that NSString can respond to. You have to cast the Swift String to NSString. – H. Al-Amri Dec 18 '18 at 14:03
  • @H.Al-Amri Please read completely before commenting. That is addressed in the question above and the comments of the accepted answer. It cannot be done; the compiler won't allow it. – Ky - Dec 18 '18 at 17:48

1 Answers1

1

I'd try

func mySwiftFunc(text: String) {
     myObjcClassInstance.perform(#selector(NSSelectorFromString("myMethod:"), with: text as NSString)
}

If that won't work try:

func mySwiftFunc(text: String) {
    let selector : Selector = NSSelectorFromString("myMethod:")
    let methodIMP : IMP! = myObjcClassInstance.method(for: selector)
    unsafeBitCast(methodIMP,to:(@convention(c)(Any?,Selector,NSString)->Void).self)(myObjcClassInstance,selector,text as NSString)
}

Probably possible without selector name "myMethod:" String dependence, but I skipped the casting gymnastics as it's not the essence of the problem.

Kamil.S
  • 5,205
  • 2
  • 22
  • 51
  • @BenLeggiero Maybe even `myObjcClassInstance.myMethod(text as NSString)` would suffice – Kamil.S Nov 27 '18 at 19:55
  • 1
    I tried that first, actually. It wouldn't compile; it _requires_ I pass it a Swift `String` – Ky - Nov 27 '18 at 22:55