Use NS_SWIFT_NAME away from the file where a property is declared?

219 Views Asked by At

I have an objective-c iOS codebase that uses a set of files generated (please don't laugh) by a tool from a SOAP WSDL file. One of the objects in the WSDL includes a property "Description". The tool dutifully - and correctly - translates this into an Objective-C object with a "Description" property. All has worked just fine for 15 years or so.

I'm in the process of converting my app to Swift because I can read the writing on the wall (despite Apple's protestations to the contrary). And the fact that swift can interact with Objective-C is a key reason that this is possible!

But alas...when accessing Objective-C code from Swift, the default is to convert the casing of the property from upper-case to camel case, so "Description" becomes "description", and conflicts with the Swift "description" property on such objective-c objects. (Does XCode raise a compile-time error on this? Of course not, but that's a separate digression). As a result, my code that references the "description" property gets the name of the class rather than the human-readable value in the "Description" (capital-D) property.

Apple recognizes this issue and provides the "NS_SWIFT_NAME" macro to specify a name to be used from Swift, and that could easily solve the problem, except that this is in code that's generated by a 3rd party tool, so it has no idea when/where to add the "NS_SWIFT_NAME" macro.

For now, I've got an insane hack on this: I've created an objective-c method visible to Swift that takes the object in question and returns the value of the "Description" field (capital-D), and that works, but (a) man that's an ugly hack, and (b) if my goal is to translate everything except the tool-generated-code to Swift, then...well, tough luck.

Any thoughts for solutions here? I can't seem to apply the "NS_SWIFT_NAME" macro in an extension or otherwise in a separate file...

2

There are 2 best solutions below

1
Geoff Hackworth On

I also thought about using an Objective-C category to extend the WDSL-generated class to provide another property for accessing the Description. It's still messy, but I think it is cleaner than passing an object to a top-level function.

Here's how I mocked it up.

First, here's the WSDL-generated class which you cannot change. It has a Description property:

// WDSLClass.h - auto-generated

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface WSDLClass: NSObject

// the `Description` property that you want to access
@property (strong, readonly) NSString *Description;

// lots of other stuff

@end

NS_ASSUME_NONNULL_END
// WDSLClass.m - autogenerated

#import "WSDLClass.h"

@implementation WSDLClass

- (NSString *)Description
{
    return @"This is the Description!";
}

// lots of other stuff

@end

To provide access to the Description property under another name I created an Objective-C extension on WSDLClass:

// WSDLClass+Extensions.h

#import "WSDLClass.h"

NS_ASSUME_NONNULL_BEGIN

@interface WSDLClass (Extensions)

// This is an alias for the `Description` property
@property (strong, readonly) NSString *getDescription;

@end

NS_ASSUME_NONNULL_END
// WSDLClass+Extensions.m

#import "WSDLClass+Extensions.h"

@implementation WSDLClass (Extensions)

- (NSString *)getDescription
{
    return self.Description;
}

The bridging header needs to import WDSLClass+Extensions.h to gain access to this. I can then do this in Swift:

let wsdlObject = WSDLClass()
print(wsdlObject.getDescription)

I.e. I can use the getDescription property (from Swift or Objective-C) to access the Objective-C Description property.

Of course, you would need to rename WSDLClass to whatever class name you need and maybe getDescription isn't the best name for the alias.

If there are mutiple objects with a Description property then you would need to create an extension for each of them (best combined into a single file for simplicity).

Granted you still need to keep a bit more Objective-C in your project, but it's a little easier to use than your current hacky workaround.

If Description isn't readonly then you would need to implement a custom getter and setter in the Objective-C category for getting/setting the underlying Description property.

0
Eric Berman On

Check this out as well. My WSDL-generated code is generating a read-write var (String) for description. And, of course, this conflicts with the (NSObject-provided?) "description" that is a get-only property for any object (and which seems to return the class name.

So in the debugger, if I do this I see that things are ambiguous:

(lldb) po self.description
error: expression failed to parse:
__ObjC.MFBWebServiceSvc_SimpleMakeModel:19:14: note: found this candidate
    open var description: String! { get set }
             ^

ObjectiveC.NSObject:105:14: note: found this candidate
    open var description: String { get }
             ^

error: <EXPR>:8:1: error: ambiguous use of 'description'
self.description
^

__ObjC.MFBWebServiceSvc_SimpleMakeModel:23:14: note: found this candidate
    open var description: String! { get set }
             ^

ObjectiveC.NSObject:109:14: note: found this candidate
    open var description: String { get }

Note that the NSObject runtime-getter is not implicitly unwrapped, but the read/write property is. So if I now type "po self.description!", it seems to explicitly return the correct underlying description read/write property.

I'm not entirely sure why that happens - I thought "description!" meant "I know description is non-nil, so unwrap it", I didn't think it was also a means of disambiguation. Am I relying here on an artifact that could change, or is this deliberate in swift?