Let's first try to understand module map file - the most important part in swift ecosystem when interpolating with C/C++ languages if you don't use a bridging header.
A module map(module.modulemap) is a small text file understood by Clang. It tells the compiler how a set of C, Objective-C, or C++ header files should be grouped into a Clang module and which of these headers make up that module's public interface.
Thanks to the module map, Clang(and therefoew swift, which embeds Clang under the hood) can:
Put differently, the module map is to Clang modules what a Packet.swift manifest is to Swift packages: a manifest that explains what belongs to the module and how to expose it.
Let's look at a typical framework bundle:
libavformat.xcframework/
├── Info.plist
└── macos-arm64_x86_64
└── libavformat.framework
├── Headers -> Versions/Current/Headers
├── libavformat -> Versions/Current/libavformat
├── Modules
│ └── module.modulemap // That is our module map, it guides swift to find your symbols.
├── Resources -> Versions/Current/Resources
└── Versions
├── A
│ ├── Headers
│ │ ├── avformat.h
│ │ ├── avio.h
│ │ ├── config.h
│ │ ├── os_support.h
│ │ ├── version_major.h
│ │ └── version.h
│ ├── libavformat
│ └── Resources
│ └── Info.plist
└── Current -> A
11 directories, 11 files
And this is our module map look like
framework module libavformat [system] {
umbrella "." // All Headers are exported as public symbols in swift, except for os_support.h
exclude header "os_support.h"
export *
}
Check the link I provided earlier, alll though the swift 5.9 interpolates with C++ directly. The underlying modulemap mechanism hasn't changed, quote this from the original post: In order for Swift to import a Clang module, it needs to find a
module.modulemap` file that describes how a collection of C++ headers maps to a Clang module.
So, that means automatically or mannually, we have to make sure that modulemap file exists.
Think about these senarios we usually compile our C++ library:
How the C++ code is Compiled | Who creates the module map? | When you have to author one manually |
---|---|---|
Xcode framework / target (You let xcode build your C++ dependency) | Xcode auto-generates it | Rarely – only if you need custom requires, link, or want to hide headers |
Swift Package Manager(You let SPM build your C++ dependency) | SPM auto-generates it when it finds an umbrella header in include/ | If you don’t provide an umbrella header or you need finer control (multiple sub-modules, add link, exclude heavy templates, hide some unused symbols, etc.) |
Plain .c/.cpp + headers in some folder (no framework, no SPM target, You use cmake or other build system) | Nobody | You must supply a module.modulemap, then add the directory to SWIFT_INCLUDE_PATHS / pass -I so Swift can find it |
So to clarify your questions:
When does building a framework benefit from having a modulemap file in its build settings?
When does building a Swift project that imports a objective-c++ framework benefit from that framework having a modulemap file?
Module map gives these advantages over a bridging header when you use Swift in your xcode project
Advantage | Bridging header | Module map |
---|---|---|
1. Pre-compiled representation (PCM) so headers aren’t re-parsed for every Swift file | NO, Every Swift file reparses the header text(Althrough it has cache as pre-compiled header (PCH), but reopen and deserialize the PCH happens for every swift file) | YES Parsed once → cached PCM → big compile-time savings, especially for large frameworks. |
2. Stable logical name you can import MyLib from Swift & Obj-C/C++ | Partial – Swift can see symbols via the bridging header but the module name is your target name (import MyApp) rather than the library’s own. | YES Explicit, reusable namespace (import MyLibCore, import MyLibExtras, etc.). |
3. Selective exposure / hiding of headers | NO All included headers become public; no sub-modules. | YES export *, exclude header.h, sub-modules (module Core {…}), etc. |
4. Automatic linker flags (link "z", link "CoolC++Lib") | NO, You must add libraries to “Link Binary With Libraries” or other-linker-flags yourself. | YES Link directives live in the map, so SwiftPM / Xcode pick them up automatically. |
5. Better incremental builds & parallelism | NO, Any change to the bridging header forces all Swift files to rebuild. | YES PCM change fingerprints allow fine-grained invalidation; Swift files compile in parallel against the cached module. |
But if your project's main language is OC, your project totally work perfectly without a module map.
It is not mandatory to have a module map, you can still stick to your old objective-c++ wrappers solution.
Yes, you can tune/add the modulemap file anytime. But 3 things to have i mind:
Shift + CMD +K
, so stale caches aren’t reused.No, Embedding (copying the .framework into the app bundle at build time) is purely a link-and-package concern. The module map is consumed before that, while compiling your Swift sources. Whether you later run Embed & Sign or link it from a system path has no impact on the need for or contents of module.modulemap.
No, static library or shared dynamic library only decides how the object code is linked and loaded at runtime, while module map happens at compile time.