I ran into an interesting issue the other day with an Objective-C API that was bridged for use in Swift. For anyone who hasn’t used this functionality before, you can designate one or more Objective-C library header files to include in the workspace by way of a “bridging header”. All of those APIs will be made available to a Swift app. Not only do you get global use of the headers’ APIs, but you get it using Swift’s syntax. It’s pretty neat.
So if you have an Objective-C API:
Swift and the compiler automatically make the following syntax available to you:
You get both the initializer (which drops the
initWith and lowercases the
Foo) as well as the instance method, which is translated verbatim.
The problem came about with a class method. Usually, these get translated from Objective-C:
In order to be used as follows in Swift:
But I had a legacy singleton class method from an upstream project:
Besides being slightly bad form for singleton method naming, it doesn’t work in Swift:
The compiler reports:
'doodad' is unavailable: use object construction 'LegacyDoodad()'
Why the problem?
Notice in the first example how the Swift compiler was able to recognize that
initWithFoo: in Objective-C was an initializer and parse it appropriately. In Swift, there is also the concept of convenience initializers, initializers marked
convenience when declared directly in Swift. These initializers generally take no arguments, providing their own sane defaults, as a way to “shortcut” longer, more verbose designated initializers. It turns out that the Swift compiler thinks that
doodad is a convenience initializer because:
- It takes no arguments.
- It is named in a way that is derivative of the class name.
Thus, it doesn’t translate
doodad and just defers to the automatically-provided default initializer (roughly analagous to
-[[LegacyDoodad alloc] init]) provided to every class, which is called like
LegacyDoodad() — also with no arguments.
The fix turns out to be pretty simple as long as you take a look at how Apple designs APIs. A good model is
UIApplication, which is always accessed via a singleton:
This is used as you’d expect in Swift:
Using a singleton naming scheme like
sharedInstance (commonly seen in third-party libraries) ensures that the Swift compiler doesn’t get fooled into thinking it’s a convenience initializer.
Here’s a way to amend the original example to work the way we’d expect:
And in Swift:
Special thanks to the following StackOverflow posts which helped me get to the bottom of this:
- How to access an Objective-C class method from Swift language
- How to call an Objective-C singleton from Swift?
The original reported issue in our project queue:
A nice blog post about singleton method naming in Objective-C:
And Apple’s reference documentation on Swift initializers:
- Jan 28, 2015: Added a link to a second StackOverflow post; added some info about default initializers when talking about why things weren't working.
- Jan 29, 2015: Fixed Objective-C class declaration syntax, which was a weird Objective-C/Swift hybrid.