Subclassing Swift class in Obj-c [Private-API Friday #1]

NSGolova
3 min readFeb 20, 2021

If you are in the migration process from Objective-C to Swift language, you maybe need to subclass some new Swift class back in objc. Today I will show you how you can do this and why you shouldn’t.

Investigation

Let’s create our guinea pig class.

If you try to compile this code, you would be greeted by the “Cannot subclass a class with objc_subclassing_restricted attribute.” error. But wait, my class is not marked by any attribute! Or is it?

Let’s open the bridging header and find our class:

Than hit Product->Perform action->Preprocess “NGObjcClass.m”

Here we can clearly see the attribute. This attribute is added by the macro “SWIFT_CLASS”.

Xcode checks for the attribute and macro existence and defines the new macro.

Implementation

Yes! Private API! Let’s trick Xcode by defining our own “SWIFT_CLASS.” I forget how to do this locally and will change GCC preferences instead. Let me know if you can make this locally.

GCC_PREPROCESSOR_DEFINITIONS=$(inherited) SWIFT_CLASS(SWIFT_NAME)=”SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA” SWIFT_CLASS_NAMED(SWIFT_NAME)=”SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA” ← put this in you favourite settings place.

This macro will be with greater priority while compiling and Xcode will not define his variant with the attribute in it.

Now when we have satisfied our weekly dose of private API, let’s discuss why the attribute was added in the first place.

Reasons

Objc is not a Swift. No, really, it’s very different languages under the hood. And with an easy bridging provided by Xcode this can be easily forgotten. I will add two simple functions to our example class:

What will happen if I call “testFunc()” on NGObjcClass in Swift and in objc? Swift will work fine, but in objc we will get this error:

The reason is that the “method” is not equal to “function”. I will not dig into details. However, will show you the “Hopper Disassembler” result for my function. As you can see, it is far from Objective-C call conventions. This behaviour will persist even if you mark the “innerTest()” with the “@objc” attribute. =(

It’s what about Apple reasons. There are several reasons more:

  • It’s a private API. So all of its problems are applicable here (stability, UB, etc).
  • Cycle bridging headers. You can’t use your subclass back in Swift, because Swift files should be compiled before your subclass, but they are using your class, so ¯\_(ツ)_/¯

Conclusions

Some rules and limitations exist for a reason. However, they shouldn’t be accepted just by the sole authority. Investigation of private APIs can be useful for better understanding system behavior and languages. It also can help to become a better public API developer. So I will start this series to share my insights from different hidden elements that I encounter.

--

--