Swift frameworks with C code
Frameworks are powerful tools to separate your codebase into smaller, more maintainable modules. Unfortunately, Xcode has some flaws if you want to mix and match Swift and C code in a framework without providing direct access to private functions. In this article we will create a Swift framework that prevents direct access to the encapsulated C code - both from Swift as well as Objective-C.
C and Swift Interop in Frameworks
Any C code that you want to wrap in a Swift framework needs to be public during compile time, either via the umbrella header or using module maps. The important part here: during compile time. Frameworks have no integrity checks that means we can apply post processing, e.g., to obfuscate private functions.
Preparing the build
Instead of putting all the C header dependencies into the umbrella header we will put them into a
module.modulemap file. The advantage: We can move our C headers into the private section of the
Headers Build Phase.
Therefore, create a
module.modulemap file in your project and set the path to it in the
Module Map File Build setting in the
Packaging section (
MODULEMAP_FILE if you are using xcconfig files), e.g.,
module.modulemap file should look like this:
So far so good. You should now be able to build your code and access it from both Swift and Objective-C. The only problem: You can still access your C header files in Swift and Objective-C.
If you have a look at the final Framework bundle you will notice that all your private header files have been copied to the
MyFramework.framework/PrivateHeaders folder. That means you can still access them via
#import <MyFramework/A.h> in Objective-C.
In Swift you can still access the C code because we put the header files in the
module.modulemap file that has been copied to
Fortunately, we can just get rid of those two problems with a bit of post processing:
- Remove the
PrivateHeadersfolder from the framework bundle.
- Create a separate modulemap and remove all
header "XYZ.h"statements, e.g., name it
- Put all of that in a
Run ScriptBuild Phase
Here’s the public modulemap:
And the run script that you should add to the end of your framework’s build phases:
You should now be able to build Swift frameworks that encapsulate C code but do not expose any private headers to any user of the framework.