An Swift/ObjC Interoperability Wrinkle: PersonNameComponents
So, I wrote a category on NSPersonNameComponents to implement some custom name-formatting logic, and ran up against an interesting problem. Try as I might, my code in the category just wasn’t visible at all from swift. I wrote code like this:
let components = PersonNameComponents(…)
components.myObjCFunction()
but whatever I would do, the compiler would complain that PersonNameComponents didn’t have a function myObjCFunction()
But, the ‘generated interface' showed that there was in fact a myObjCFunction(), and if I wrote more code in that .m file, like a category on NSString, I’d be able to use it from swift, so the module was getting imported correctly, and there wasn’t a problem with the header includes. What on earth was wrong?
After spending far too long chasing some rabbit holes, I figured out that PersonNameComponents and NSPersonNameComponents are not the typical pair of swift/ObjC Foundation classes. NSPersonNameComponents is indeed an Objective C class, but PersonNameComponents is a struct (not a class). This mucked with the visibility of the selectors in the category
It turns out this code worked just fine:
let nsComponents = NSPersonNameComponents(…)
nsComponents.myObjCFunction()
And I was happy for about two minutes until the compiler complained about this:
let formatter = PersonNameComponentsFormatter(…)
let nsComponents = NSPersonNameComponents(…
let formattedName = formatter.string(from:nsComponents)
Apparently, PersonNameComponentsFormatter is equivalent to NSPersonNameComponentsFormatter, but it takes a PersonNameComponents struct as an argument here, and can’t take an NSPersonNameComponents.
The solution was easy, once I figured out that the compiler would be happy with
let formattedName = formatter.string(from:nsComponents as PersonNameComponents)
So, tl;dr: sometimes the swift equivalent of an Objective-C class isn’t a swift class, and in that case, writing a category on the Objective-C class isn’t visible to the swift equivalent. In that case, one needs to actually use the objectiveC class in swift with the NS name and everything.