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:
- Remove the
PrivateHeaders
folder from the framework bundle. - Create a separate modulemap and remove all
header "XYZ.h"
statements, e.g., name itpublic.modulemap
. - 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.