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., $(SRCROOT)/MyFramework/module.modulemap.

Your module.modulemap file should look like this:

# module.modulemap

framework module MyFramework {
    umbrella header "MyFramework.h"  

    export *
    module * {export *}
    # All C Files you want to wrap in your Swift code

    header "A.h"
    header "B.h"

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 MyFramework.framework/Modules/module.modulemap.

Fortunately, we can just get rid of those two problems with a bit of post processing:

  1. Remove the PrivateHeaders folder from the framework bundle.
  2. Create a separate modulemap and remove all header "XYZ.h" statements, e.g., name it public.modulemap.
  3. Put all of that in a Run Script Build Phase

Here’s the public modulemap:

# public.modulemap

framework module MyFramework {
    umbrella header "MyFramework.h"  
    export *
    module * {export *}

    # No more C Headers here

And the run script that you should add to the end of your framework’s build phases:

# Delete PrivateHeaders folder
# Remove module.modulemap file
rm ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap

# Copy public.modulemap file and rename it to module.modulemap
cp ${SRCROOT}/test/public.modulemap ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap

# Append the Swift module so you can access you Swift code in Objective-C via @import MyFramework.Swift
echo "module ${PRODUCT_NAME}.Swift { header \"${PRODUCT_NAME}-Swift.h\" }" >> ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap

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.