Paulo Andrade

Keeper of Secrets.

twitter github stackoverflow linkedin email
Mixing Swift, Objective-C, SPM and Static Frameworks
Mar 7, 2022
3 minutes read

There’s always something new in a developer’s world: a new framework, a new IDE, a new CPU architecture, a new language, a new dependency management system, etc. For any moderately large project it’s impossible to keep up. Nor should you even try, or you’d just spend your time rewriting code.

I’ve been working on Secrets for the past 10 years. There are parts of it that were written before ARC. You can still find retain and release calls today!

As I mix more and more of the new with the old, I get to enjoy some tricky issues.

The tricky issue

The core of Secrets is composed of many small cohesive static frameworks that are then linked together in a larger SecretsCore dynamic framework. This framework is then embedded in the main Secrets app and all the app extensions (Password AutoFill, Safari Extension, Siri Intents, Secrets Helper, etc). I’ve wrote about modularizing your project in the past.

Recently I’ve added a swift package dependency to the project using SPM which posed a problem with my current setup. When one of my Objective-C static frameworks included the swift module via @import Foo; the app would fail to build with:

error: module 'Foo' not found
@import Foo;
^
<unknown>:0: error: could not build Objective-C module 'MyObjCStaticFramework'

Google told me I was not the first one encountering this. But this issue was supposed to be fixed in Xcode 12.

The fix

While discussing this issue with Google, Apple Developer Forums and Stack Overflow, someone said that if the module is actually added to the “Link Libraries” build phase the problem goes away.

Now, I don’t actually want to do this since this is a static framework. But I gave it a try and voilá. It does find the module, only to fail later down the line with duplicate symbols (which was expected and the reason why I didn’t want to link it in the first place).

So I look at the differences between the build commands for when I link the module and not. And the difference is that Xcode will automatically add a -fmodule-map-file flag to the compiler when the module is supposed to be linked.

So I look at the build folder and available Xcode variables and replace the module in the “Link Libraries” phase with and manually added “Other C Flag”:

-fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/Foo.modulemap

Everything builds and I’m back on track! Or so I thought…

The return of the tricky issue

Not much time later and I’m importing that ObjC static framework from a Swift file and get hit with the same issue…

Except this time I can’t pass the -fmodule-map-file to the Swift compiler, since that’s a C compiler flag.

After some time pressuring Google and friends to talk, they have no answers for me at this point. I give up on them and try to look for a flag in the swift compiler that allows me to do the same thing.

The final solution

$ swift --help
(...)
-Xcc <arg>              Pass <arg> to the C/C++/Objective-C compiler
(...)

Could this be it? I hastily add the -Xcc -fmodule-map-file=$(GENERATED_MODULEMAP_DIR)/Foo.modulemap to the “Other Swift Flags” and all is good again.

In the end I’ve only spent like 3 hours on these two issues. That’ll teach me to try and bring modern tools to an old project 😒.

Kidding aside, the fact that I was able to get around the issue by tweaking settings in Xcode is what scares me the most about SPM-only projects. SPM takes away so much of my control over the build process that I don’t think I’ll go all-in anytime soon.



Back to posts