objective-cswiftxcodelanguage-interoperabilitymodule-map

Objective-C and Swift interoperability inside framework with module mapping


Context

Years ago my company built an Objective-C framework that uses a C library and now we're trying to convert it to Swift. Since we need to use this C++ library, instead of completely eliminating objective-c code from the base we need to maintain the interoperability between these two languages. The key thing is that we need to do it in a way that not all interfaces are available to the app using the framework - that's where my problem relies on.

Attempts

First thing I searched was about how to use a C++ library with Swift, from all that I searched we needed to create a bridge between C++ and Swift using Objective C. So I started to focus on trying to create the interoperability between swift and objective c, whereas the C++ library comes naturally from the existing connection between C++ and Objective-C.

After a thousand of google searches and safari tabs, I discovered that inside an app all we needed to do is create a bridging header and we're good to go, not exactly easy nor difficult but kinda toilsome, but inside frameworks we don't have bridging headers, the only way this can achieved are ModuleMaps, these guys act like bridging headers encapsulating ObjC frameworks inside modules.

So I tried this and worked! Hurray!

But the problem is: in order to make this work I needed to make my headers public (and my new swift classes public as well). So more searching came along and I found out that are Private module maps. I tried to recreate one from tutorials like this:

framework module MyDeprecatedObjCFramework {
    umbrella header "MyDeprecatedObjCFramework.h"

    export *

    explicit module Private {
         header "Path/To/Private/Header.h"
    }
}

but the compiler wasn't regarding the Private keyword at the modules' name ending, and I was being able to access it from outside the framework. Setting a name for it wasn't working either (e.g. MyDeprecatedObjCFrameworkPrivate or MyDeprecatedObjCFramework_Private or MyDeprecatedObjCFramework-Private and according to clang documentation the current pattern is _Private).

Creating one file apart and setting it as the Private Module Map in the build settings didn't work for me either, in this case there were two attempts, first one was like this:

module MyDeprecatedObjCFramework_Private {
    header "Path/To/Private/Header.h"
    export *
}

In this case, the module couldn't be accessed by anyone, even internally by the framework. In a nutshell it wasn't being copied to the outputted framework inside Products Xcode folder. Adding the keyword framework before module resulted in include of non-modular header inside framework module.

I also found out that these clang things weren't stable enough and could break between major versions. From apple itself it doesn't seem to be a viable workaround and the only solution for this guy was to create a lint to prevent outer apps to import private headers/swift classes, which seems to me kinda of messy.

Final question

Is it possible to create interoperability between objective-c and swift inside a framework? Is this viable? Would it be better to recreate the entire framework from ground up? Or it's better to split it in other swift frameworks?


Solution

  • I had similar issues but I did not find an ideal solution. To my knowledge you listed all the alternatives. Apple documentation is quite clear:

    To use the Objective-C declarations in files in the same framework target as your Swift code, you’ll need to import those files into the Objective-C umbrella header—the master header for your framework. Import your Objective-C files by configuring the umbrella header:

    and as you stated even Apple discourages you from using module maps and recommends puting your Objective-C code in a separate target.

    If you have time, recreating the entire framework from ground up may be the best solution.