How to compile C code into Swift Framework How to compile C code into Swift Framework xcode xcode

How to compile C code into Swift Framework


Just to rephrase your problem: You basically want to wrap C code in Swift and prevent direct access to the encapsulated C code. That means to prevent access from both Swift as well as Objective-C.

That is possible but requires some adjustments to your build process and some post processing.

C and Swift Interop in Frameworks

Any C code that you want to wrap in Swift 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 which means you can apply post processing to them (e.g., create a fat binary framework with lipo). And that's what we need to do here.

Preparing the build

Instead of putting all the C header dependencies into the umbrella header you can also put them into the module.modulemap file. That has one advantage: You can make your headers private in 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 in xcconfig files), e.g., $(SRCROOT)/MyFramework/module.modulemap.

Your module.modulemap file should have the following content:

# module.modulemapframework 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.

Postprocessing

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.modulemapframework 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 folderrm -rf ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/PrivateHeaders# Remove module.modulemap filerm ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap# Copy public.modulemap file and rename it to module.modulemapcp ${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.Swiftecho "module ${PRODUCT_NAME}.Swift { header \"${PRODUCT_NAME}-Swift.h\" }" >> ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap

Hope that helps!


There is a solution, although it's not recommended. You can use @_silgen_name inside your .Swift file instead of using C header.