14

I have swift classes mixed in with my Objective-C code. With Swift 2.3, everything was fine and worked as expected.

I recently converted to Swift 3, and it updates several API calls because of all the renaming that occurred for Swift 3. That's fine; I get that.

But what's not fine is that Swift 3 seems to have renamed a method in one of my Objective-C classes. I own the Objective-C class and I called the method what I wanted: readDeliveryInfoItems. But now, after converting to Swift 3, I can't call .readDeliveryInfoItems() anymore in my Swift class. It's telling me it has been renamed to .readItems().

That makes no sense. And the Objective-C class still calls the method readDeliveryInfoItems, so there is something under the covers going on here.

I have tried renaming the Objective-C readDeliveryInfoItems method to readDeliveryInfo, building (Swift fails because it says that the readInfo() method doesn't exist, which is good), and then renaming the method back to readDeliveryInfoItems. However, when I build after this, Swift goes back to thinking the method is called readInfo(). I was hoping this would trick Xcode into refreshing the Swift bridging and renaming the method back to the correct name readDeliveryInfoItems(), but it did not.

How can I fix this?

UPDATE TO ADD MORE INFO

The interface of my Objective-C class has this function declaration:

- (nullable NSArray<XMPPDeliveryInfoItem *> *)readDeliveryInfoItems;

But in the Generated Interface (see MartinR's comment below) for that class, the function declaration is this instead:

open func readItems() -> [XMPPDeliveryInfoItem]?

There are other functions in that class that are similar to the readDeliveryInfoItems function, such as this one:

- (nullable NSArray<XMPPDeliveryInfoItem *> *)sentDeliveryInfoItems;

And they look correct in the Generated Interface:

open func sentDeliveryInfoItems() -> [XMPPDeliveryInfoItem]?

So I can't figure out why I'm having this problem with only the one function.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
Ethan G
  • 1,353
  • 2
  • 15
  • 31
  • 2
    You can use `NS_SWIFT_NAME` to control the Swift name mapping, see e.g. http://stackoverflow.com/questions/39203235/use-objective-c-method-in-swift-seems-crossing-keyword-of-swift-language for an example. – Martin R Oct 20 '16 at 14:26
  • 1
    The Swift 3 compiler does **not** change anything in your Objective-C files, it adapts only the Swift 3 naming convention on the Swift side. – vadian Oct 20 '16 at 14:29
  • @MartinR thanks...yeah that works...but I really don't want to have to do that in this case for this one method, and I definitely don't think I should have to. – Ethan G Oct 20 '16 at 14:40
  • In any case, it would be helpful to see a (minimal) self-contained example of the ObjC interface and the Swift mapping. – Martin R Oct 20 '16 at 14:55
  • @MartinR when you say "Swift mapping", what do you mean? How can I view that? I know how to view the bridging header and the -Swift.h file, but none of those show how the Objective-C code is being compiled to work in Swift. The bridging header shows what you want to be able to use in Swift, and the -Swift.h file shows what you can use from Swift in your Objective-C. Is there something else to look at in regards to the "Swift mapping"? – Ethan G Oct 20 '16 at 15:03
  • 3
    You can view the "Generated Interface" of C/Objective-C headers, see https://developer.apple.com/library/content/qa/qa1914/_index.html for instructions. – Martin R Oct 20 '16 at 15:05

2 Answers2

20

The translation process is described in detail in

The relevant part for your question is (emphasis mine):

Prune a match for the enclosing type from the base name of a method so long as the match starts after a verb. For example,

extension UIViewController {
  func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)? = nil)
}

becomes:

extension UIViewController {
  func dismissAnimated(flag: Bool, completion: (() -> Void)? = nil)
}

This pruning algorithm is – as far as I can see – implemented in StringExtras.cpp (and uses a lot of heuristics), and PartsOfSpeech.def contains a list of words which are considered a verb, such as

VERB(dismiss)
VERB(read)
VERB(send)

but not VERB(sent). That explains why – simplifying your example slightly –

@interface DeliveryInfo : NSObject
-(void)readDeliveryInfoItems;
-(void)sentDeliveryInfoItems;
@end

becomes

open class DeliveryInfo : NSObject {
    open func readItems()
    open func sentDeliveryInfoItems()
}

The type name is pruned after the verb "read", but not after the non-verb "sent". (You can verify that by changing the second method name to sendDeliveryInfoItems which is then mapped to sendItems().)

You can override the mapping with NS_SWIFT_NAME:

-(void)readDeliveryInfoItems NS_SWIFT_NAME(readDeliveryInfoItems());
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • And the compiler can't tell that you mean "read" (the past participle of the verb *to read*) and not the active verb. Silly English for making those tenses the same for that word... ;) +1 for `NS_SWIFT_NAME` if you want to keep either the longer construction for "read" or the shorter construction for "sent". – rickster Oct 21 '16 at 05:41
  • @rickster: Apparently the compiler does not *listen* carefully, isn't "read" pronounced differently from "read"? – Martin R Oct 21 '16 at 06:14
  • Good point. You should file a bug on that... I bet Apple's accessibility folks would want to get right on it. Till then, I suppose the workaround is to use IPA for all identifiers. – rickster Oct 21 '16 at 07:50
  • @MartinR Thanks very much for this...very informative. – Ethan G Oct 21 '16 at 13:22
0

Roughly speaking (and I am oversimplifying), if the suffix on a method name is a verb object matching the return type, the suffix is dropped.

A simpler example would be a method called readString that returns an NSString.

Your method falls within those parameters (I told you I was oversimplifying but you can see, roughly, how that's true), so you get the treatment.

Personally, I regard this as a bug, especially because in some cases the change can result in name clash and make calling the method impossible (esp. when the Objective-C API is not yours and you cannot change it). For an example, see this question: Swift 3 (Omit Needless Words) causing two functions to have the same name

Community
  • 1
  • 1
matt
  • 515,959
  • 87
  • 875
  • 1,141