Bridging Headers: Letting Swift See Your Objective-C Code
Explain what a bridging header is, precisely when one is needed versus not needed, how imports and module boundaries within it work, the distinction between importing a whole framework versus individual files, and why keeping a bridging header small is a practically important discipline rather than a stylistic preference.
The previous lesson covered the generated Objective-C interface — the mechanism that lets Objective-C code see Swift code. A bridging header does the mirror-image job: it is how Swift code, within the same app target, sees Objective-C code. The two mechanisms look superficially similar (both are, at bottom, 'a header that makes one language's declarations visible to the other'), but they work in opposite directions and are produced differently — the Objective-C interface is auto-generated by the Swift compiler with no developer effort required, while a bridging header is something you (or Xcode, prompted by you) actually create and maintain by hand, listing exactly which Objective-C headers you want Swift to be able to see. Because it's a real, editable file that grows however you let it grow, a bridging header is also one of the more common places where a mixed-language project accumulates avoidable technical debt — which is exactly why the spec calls out avoiding oversized bridging headers as its own practically important topic, and why this lesson treats it that way.
What a Bridging Header Actually Is
A bridging header is a single Objective-C header file, conventionally named ProductModuleName-Bridging-Header.h, that exists purely to be read by the Swift compiler on behalf of the Swift code in the same app target. It contains nothing but ordinary #import statements pulling in the Objective-C headers whose declarations you want visible from Swift; it is not itself a place to declare new Objective-C classes or write implementation code, it's purely a list of doorways into your existing Objective-C headers. When you add your first Objective-C file to a Swift-based Xcode target (or vice versa), Xcode typically offers to create this file for you automatically and wires up the build setting that points to it — you rarely create one entirely from scratch by hand, but you are expected to edit its contents as your project grows.
It is worth being explicit about the direction here, since it is easy to conflate with the previous lesson's material: the bridging header is the inverse of the generated Objective-C interface. The generated -Swift.h interface lets Objective-C code see Swift declarations, and is produced automatically by the compiler with zero manual header-writing. The bridging header lets Swift code see Objective-C declarations, and is a real file you maintain by hand, with content you choose. Both exist to solve the same underlying problem — Objective-C and Swift are different languages with no native way to see each other's declarations — but they solve it for opposite directions, with opposite amounts of manual involvement.
Concretely, once a bridging header exists and is configured as the target's 'Objective-C Bridging Header' build setting, every Swift file in that target automatically gets visibility into everything the bridging header imports — there is no per-Swift-file #import or import statement needed for Objective-C code reached through the bridging header; it behaves as if every declaration it pulls in were simply part of the ambient, always-visible vocabulary available to every Swift file in the target, similar to how Foundation types are always available in a Swift file that imports Foundation once.
• A bridging header (ProductModuleName-Bridging-Header.h) is a real, hand-maintained Objective-C header file, not auto-generated. • It contains only #import statements pulling in the Objective-C headers you want visible to Swift — no implementation code of its own. • It is the inverse of the previous lesson's generated -Swift.h interface: this one lets Swift see Objective-C, not the other way around. • Once configured, every Swift file in the target automatically sees everything the bridging header imports, with no per-file import needed.
When a Bridging Header Is Actually Needed
This is one of the most commonly confused points in mixed-language projects, so it deserves a direct, clear statement: a bridging header is needed only for exposing Objective-C code to Swift within the same app target — that is, Objective-C source files that live directly inside the same build target as your Swift code, compiled together as one product. It is not needed, and should not be used, for consuming a separate framework's already-public Objective-C API — a system framework like UIKit or a third-party framework distributed as its own module — because a properly built framework already exposes its API to Swift through its own module map, without any bridging header involved at all.
The practical test is: is this Objective-C code compiled as part of the same target as my Swift code, with no framework boundary between them? If yes — a handful of Objective-C helper classes living directly alongside your Swift files in one app target, for instance — a bridging header is the right and, in fact, the only mechanism for exposing them to Swift. If no — you're consuming CocoaPods-, Swift-Package-Manager-, or Carthage-distributed Objective-C code packaged as its own separate framework or library target — you instead simply import TheFrameworkName in Swift, exactly as you would import any Swift framework, and its module map (a small manifest describing which headers form its public interface) handles exposing it, with no bridging header anywhere in the picture.
Conflating these two situations is a common source of real confusion: a developer who has only ever seen 'add Objective-C code, get a bridging header prompt' sometimes assumes every Objective-C dependency needs bridging-header treatment, and goes looking for a bridging header entry point on a properly modularized third-party framework that neither has nor needs one. The distinguishing question is always whether a module boundary already exists between the Swift code and the Objective-C code in question — within one target, there is no module boundary, so a bridging header is required to cross the language gap; across a framework boundary, the module map already does that job.
• Needed: exposing Objective-C source files inside the SAME app target to Swift code in that same target. • Not needed: consuming a separate framework's public Objective-C API — that uses its own module map, imported with import FrameworkName. • The test: is there already a module/framework boundary between the Swift code and the Objective-C code? If yes, no bridging header; if no (same target), yes. • A common confusion is assuming every Objective-C dependency needs a bridging header — properly modularized frameworks don't.
Imports Inside the Bridging Header
The contents of a bridging header are almost always trivially simple: a short list of #import "SomeObjectiveCClass.h" lines, one per Objective-C header (or small group of related headers) you want visible to Swift, exactly the same #import syntax you'd use inside any other Objective-C file. There is no special bridging-header-specific import syntax to learn — it behaves like an ordinary Objective-C header file being preprocessed and read, because that is literally what it is; the only thing special about it is the one build setting pointing the Swift compiler at it.
A typical, well-kept bridging header for a small mixed-language target might look like little more than a handful of lines: #import "EHLegacyNetworking.h", #import "EHImageCache.h", #import "EHKeychainHelper.h" — each one a genuinely still-needed Objective-C class that Swift code in the target actually calls. Every import you add here is a small, permanent addition to what Swift must reprocess, which motivates keeping the list itself intentional and current rather than letting it accumulate imports for classes that were later rewritten in Swift or removed altogether.
• Bridging header contents are ordinary #import lines, using standard Objective-C import syntax — nothing bridging-header-specific. • Each #import should correspond to Objective-C code genuinely still needed by Swift in the same target. • Stale imports (for classes since rewritten in Swift or deleted) should be removed, not left to accumulate.
Module Boundaries: Why a Bridging Header Doesn't Cross Them
A bridging header's effect is scoped entirely to the one target it's configured for — it does not, and cannot, reach across into a different framework or module's build. This follows directly from the previous section's distinction: within a single target, Objective-C and Swift files are compiled together as one product with no module boundary between them, and the bridging header is precisely the mechanism that patches over the language gap that remains even without a module boundary. Across an actual framework/module boundary, a different, more structured mechanism already exists — the module map — and a bridging header has no role there at all; you cannot use a bridging header to reach into another framework's private (non-public) Objective-C headers, for instance, because that framework's module map, not your app target's bridging header, controls what's exposed.
This is why splitting a large, unwieldy pile of legacy Objective-C code out of your main app target and into its own proper framework (discussed further below) genuinely changes which mechanism applies to it: code that used to be reached via a line in the app target's bridging header is, after that split, reached via import and the new framework's own module map instead — a structurally different, and for a large body of code, generally better-scaling mechanism.
• A bridging header only applies within the single target it's configured for — it cannot reach into a different framework/module. • Crossing an actual module boundary is the module map's job, not the bridging header's. • Moving Objective-C code out of the app target into its own framework changes how Swift reaches it — from a bridging-header import to an ordinary import FrameworkName backed by that framework's module map.
Framework Imports: Umbrella Header vs. Individual Files
Within a bridging header, you have a choice, when the Objective-C code in question is organized as a framework with its own umbrella header, between importing that single umbrella header (which itself re-exports every public header the framework wants exposed, in one #import <SomeFramework/SomeFramework.h> line) and importing individual Objective-C files directly, one by one, by their own filenames. Importing the umbrella header is the simpler, more common choice for a framework you genuinely use broadly — it mirrors how you'd import that framework from another Objective-C file, and keeps the bridging header itself short regardless of how many individual classes the framework actually contains.
Importing individual files directly instead — #import "EHNetworkClient.h" rather than pulling in an entire umbrella header — makes sense when you only need a small, specific slice of a larger body of Objective-C code and deliberately don't want Swift (or the build) pulled through everything else that a broad umbrella import would drag in. This is less about correctness (both approaches work) and more about being deliberate: importing individual files keeps the bridging header's contents an honest, specific list of exactly what Swift actually depends on, which pays off directly in the next section's discussion of keeping bridging headers lean.
In practice, most real bridging headers end up being a mix: perhaps one umbrella import for a broadly-used internal Objective-C framework, plus a handful of individual-file imports for a few remaining standalone legacy classes that haven't yet been modularized or rewritten. Neither pattern is wrong in isolation; the judgment call is choosing whichever keeps the bridging header's contents an accurate, minimal reflection of what Swift genuinely needs.
• Importing a framework's umbrella header (#import <Framework/Framework.h>) is simple and mirrors ordinary Objective-C framework usage. • Importing individual Objective-C files directly is more deliberate and keeps the bridging header a precise list of actual Swift dependencies. • Most real bridging headers mix both patterns depending on how broadly a given piece of Objective-C code is actually used from Swift.
Avoiding Huge Bridging Headers: A Practically Important Discipline
This deserves its own dedicated treatment, because it is one of the most concrete, measurable pieces of technical debt a growing mixed-language project can accumulate. A bridging header that has quietly grown to dozens of #import lines, pulling in Objective-C headers from every corner of a legacy codebase, becomes a genuine maintenance liability: it is hard to know, just by reading it, which of those imports any given piece of Swift code actually still depends on, and it becomes a magnet for further unrelated imports, since adding 'just one more' to an already-huge file feels low-cost in the moment even though the file's overall health keeps degrading.
The cost is not merely aesthetic — it is measurable in build time. Swift must reprocess the entire bridging header on any rebuild where it, or any header it transitively imports, is considered to have changed; a bridging header with dozens of imports spanning much of a legacy codebase means a huge swath of Objective-C headers gets reprocessed far more often than the actual, narrow set of declarations any particular Swift file needs would require. On a large project, this is a well-known, real contributor to slow incremental build times — developers waiting on Swift compilation after touching a single, unrelated Objective-C header somewhere in a sprawling bridging header is a direct, observable symptom of this problem, not a theoretical one.
The better long-term pattern for a codebase of any real size is to stop relying on one large, catch-all bridging header at the app-target level at all, and instead wrap cohesive chunks of Objective-C code in their own proper framework, each with its own module map — the mechanism described in the module-boundaries section above. A properly modularized framework exposes exactly its intended public API to Swift through import, gets its own independent build unit (so unrelated changes elsewhere don't force it to rebuild), and simply doesn't have the 'accumulate forever' failure mode a single shared header file does, because a module map is a more structured, more surgical description of what's public than an ad hoc list of imports.
Where a bridging header is still the right tool — genuinely small mixed-language targets, or a handful of legacy classes not (yet) worth splitting into their own framework — the practical discipline is simply keeping it narrowly scoped: import only what Swift in that target genuinely still needs, prefer individual-file imports over a broad umbrella import when only a slice of a larger body of code is actually used, and treat removing a now-unused import from the bridging header as routine cleanup, the same way you'd remove an unused variable, rather than something that only happens during an occasional larger refactor.
• A bridging header with dozens of unrelated imports is hard to reason about and becomes a magnet for further, low-scrutiny additions. • Swift reprocesses the ENTIRE bridging header on relevant rebuilds — a huge one measurably slows incremental build times project-wide. • The better long-term pattern for larger codebases: wrap Objective-C code in its own proper framework/module (with a module map) instead of one large shared bridging header. • Where a bridging header remains the right tool, keep it narrowly scoped and treat removing unused imports as routine, ongoing cleanup.
Putting It Together: A Bridging Header in a Real Mixed-Language Target
Picture a Swift-first iOS app target that still depends on three small legacy Objective-C classes directly inside the same target: a keychain helper, an image-caching class, and a networking client not yet ported to Swift. This is exactly the case a bridging header exists for — the code lives in the same target, so there's no framework module map to lean on, and Swift needs a way to see those three classes' headers. The bridging header for this target would sensibly contain three individual-file imports, one per class, and nothing else: a short, honest, easy-to-audit list.
Now picture that same app six months later, after a team has extracted a broader Bluetooth-accessory SDK, still written in Objective-C, into its own internal framework with a proper module map, because it had grown large and was shared with a second app target too. From that point on, Swift code no longer reaches that SDK through the bridging header at all — it reaches it with import EHAccessorySDK, backed by that framework's own module map, exactly as described in the module-boundaries section. The bridging header stays small and focused on the few remaining same-target legacy classes, while the larger, more actively used body of Objective-C code gets the more scalable, framework-based treatment its size and shared use actually warrant.
This progression — start with a small bridging header for genuinely same-target Objective-C code, and graduate any body of Objective-C code that grows large or gets reused across targets into its own real framework — is the practical shape the spec's 'avoiding huge bridging headers' guidance takes in a real, evolving codebase, rather than a one-time cleanup task.
• Same-target legacy classes: reached via a small, individual-file bridging header import each. • A larger or cross-target Objective-C SDK: extracted into its own framework with a module map, reached via import instead of the bridging header. • The healthy long-term pattern is graduating growing Objective-C code out of the bridging header and into its own framework, not letting the bridging header absorb it.
Common Confusions to Watch For
Because this lesson's mechanism looks similar to the previous lesson's generated Objective-C interface, it's worth explicitly separating the confusions that tend to arise. First: a bridging header is never auto-generated the way ProductModuleName-Swift.h is — Xcode may offer to create an initial, empty one and wire up the build setting, but its contents are always something you write and maintain, never something the compiler regenerates from your Swift code. Second: a missing symbol error when calling Objective-C code from Swift is not automatically a 'need a bridging header' problem — if the Objective-C code in question lives in a separate framework, the fix is almost always an import statement for that framework (or a module-map/target-membership issue), not an edit to a bridging header that may not even be the relevant mechanism there at all.
Third, and closely related to the build-time discussion above: a bridging header import is not free, and does not become 'free' just because it compiled successfully once — every import added to it is a standing cost paid by Swift on future rebuilds, which is exactly why the discipline of keeping it minimal, reviewed occasionally, and pruned of stale entries is a genuinely practical habit for any team working across this language boundary, not a nice-to-have.
• A bridging header's contents are always hand-maintained — never auto-generated, unlike the Swift → Objective-C generated interface. • A missing-symbol error calling Objective-C from Swift across a framework boundary usually means a missing import, not a bridging-header edit. • Every bridging-header import is a standing, ongoing rebuild cost, not a one-time cost paid only when first added.
A Minimal, Well-Scoped Bridging Header
Every line is an ordinary Objective-C #import — no bridging-header-specific syntax exists. This file is configured via the target's 'Objective-C Bridging Header' build setting. Every Swift file in the target automatically sees EHKeychainHelper, EHImageCache, and EHLegacyNetworkClient with no per-file import needed. A framework's public API (e.g. a CocoaPods dependency) does NOT belong here — it's imported in Swift with import FrameworkName instead.
// SampleApp-Bridging-Header.h
//
// Exposes same-target legacy Objective-C classes to Swift.
// Keep this list narrow: only classes Swift in THIS target still
// depends on directly. Anything reused across targets belongs in
// its own framework with a module map instead.
#import "EHKeychainHelper.h"
#import "EHImageCache.h"
#import "EHLegacyNetworkClient.h"Code Examples
Two small, standalone Objective-C classes live directly inside the app target alongside the Swift code, with no framework or module boundary involved — exactly the case a bridging header exists for. Any Swift file in the target can now write let helper = EHKeychainHelper() with no import statement of its own needed — the bridging header made these two classes part of the target's ambient Swift-visible vocabulary.
EHCoreUtilities is organized as its own internal Objective-C framework with an umbrella header that re-exports its full public API. Importing that one umbrella header pulls in everything the framework wants exposed, in a single line. This differs from the module-map case in the sections above only in mechanism, not intent: if EHCoreUtilities were consumed from a different target that has no direct same-target Objective-C files needing bridging, it would instead simply be import EHCoreUtilities in Swift with no bridging header involved at all — the umbrella-header-via-bridging-header approach here is specifically because this target still needs the bridging header for other same-target classes anyway.
Common mistake: Reaching for a broad umbrella-header import out of convenience, when only two or three specific classes from a large framework are actually used, defeats the purpose of keeping the bridging header's contents an honest reflection of real dependencies — prefer individual-file imports in that case.
SomeThirdPartySDK is distributed as its own separate framework (via CocoaPods, SPM, or similar), with its own module map already exposing its public Objective-C API to Swift. The correct fix is to delete this line entirely and simply write import SomeThirdPartySDK at the top of whichever Swift files need it — the module map already handles exposure across that framework boundary, and routing it through the bridging header instead adds an unnecessary, confusing extra layer, and in some configurations doesn't even work correctly for a framework target.
Common mistake: Adding a separate framework's umbrella header to the app target's bridging header, rather than using an ordinary Swift import for that framework, conflates the two entirely different mechanisms this lesson set out to distinguish.
// SampleApp-Bridging-Header.h
#import "EHKeychainHelper.h"
#import "EHImageCache.h"// SampleApp-Bridging-Header.h
#import <EHCoreUtilities/EHCoreUtilities.h>// SampleApp-Bridging-Header.h
//
// WRONG: this is a separate CocoaPods framework's public header,
// not same-target code — it does not belong here.
#import <SomeThirdPartySDK/SomeThirdPartySDK.h>The Swift → Objective-C Bridging Header Path
Swift code in the target sees the Objective-C declarations reachable through the bridging header's #import list — the inverse path from the previous lesson's Objective-C Header → Clang Importer → Swift Interface pipeline.
Swift
↓
Bridging Header
↓
Objective-C HeadersImportant Rules
A bridging header is needed only for exposing same-target Objective-C code to Swift, never for a separate framework's already-public API.
A separate framework's Objective-C API is consumed with an ordinary Swift import FrameworkName, backed by that framework's own module map.
A bridging header contains only #import statements, using ordinary Objective-C import syntax — no implementation code belongs in it.
A bridging header's effect is scoped to its one target; it cannot reach across an actual module/framework boundary.
Swift reprocesses the entire bridging header on relevant rebuilds, so its size has a direct, measurable build-time cost.
For a large or cross-target body of Objective-C code, wrap it in its own proper framework with a module map rather than growing the bridging header.
Keep a bridging header narrowly scoped to what's actually used, and remove stale imports as routine cleanup.
Why This Matters: Build Times on a Growing Team
On a real, actively developed app with dozens of engineers, a bloated bridging header is one of those problems that starts invisible and becomes an everyday tax: individually, each added #import felt harmless, but collectively they turn every relevant Swift rebuild into a rebuild of a huge swath of the legacy Objective-C surface area too. Teams that notice this early and respond by extracting cohesive, growing, or cross-target chunks of Objective-C code into their own small frameworks (each with a proper module map) keep both their build times and their bridging header's contents legible over years of growth; teams that don't tend to end up with a bridging header nobody fully understands anymore, that everybody is afraid to prune, and that quietly costs the whole team minutes of build time on nearly every incremental build — exactly the practically important failure mode this lesson's spec explicitly calls out.
import SomeThirdPartySDK // in the Swift file(s) that need it, with nothing added to the bridging header
A separate framework already exposes its public Objective-C API to Swift through its own module map; routing it through the app target's bridging header instead conflates two different mechanisms and adds an unnecessary layer.
Adding dozens of #import lines to one shared bridging header over the life of a growing project, never removing stale ones
Periodically pruning unused imports, and extracting any large or cross-target chunk of Objective-C code into its own framework with a module map instead of growing the shared bridging header further
Swift reprocesses the whole bridging header on relevant rebuilds; letting it grow unchecked is a well-known, measurable contributor to slow incremental Swift build times, not just a readability concern.
Writing class declarations or method implementations directly inside the bridging header file
Keep the bridging header to #import statements only; put actual Objective-C class declarations and implementations in their own ordinary .h/.m files
A bridging header exists purely as a list of doorways into existing Objective-C headers for Swift to see — it is not a place to author new Objective-C code.
Assuming a missing-symbol error calling Objective-C code from Swift always means 'add it to the bridging header'
First check whether the Objective-C code lives in the same target (bridging header is the right fix) or a separate framework (an import FrameworkName statement, or a target-membership/module-map issue, is the right fix instead)
Conflating the two directions/mechanisms — bridging header for same-target code, import + module map for a separate framework — leads to editing the wrong file and not resolving the actual error.
What is a bridging header, precisely? A. An auto-generated header exposing @objc Swift declarations to Objective-C B. A hand-maintained Objective-C header, containing #import statements, that lets Swift code in the same target see Objective-C declarations C. A configuration file listing build settings for a mixed-language target D. A Swift file that re-declares Objective-C classes in Swift syntax
B. A hand-maintained Objective-C header, containing #import statements, that lets Swift code in the same target see Objective-C declarations — A bridging header (ProductModuleName-Bridging-Header.h) is a real, developer-maintained Objective-C header consisting of #import statements, configured via a build setting, that exposes Objective-C declarations to Swift code in the same target — the inverse of the auto-generated Objective-C interface covered in the previous lesson.
When is a bridging header actually needed? A. Whenever any Objective-C code exists anywhere in the project, including separate frameworks B. Only for exposing Objective-C code that lives in the SAME app target to Swift code in that target — not for a separate framework's already-public API C. Only when using SwiftUI instead of UIKit D. Only for exposing Swift code to Objective-C
B. Only for exposing Objective-C code that lives in the SAME app target to Swift code in that target — not for a separate framework's already-public API — A bridging header is needed only within a single target with no module boundary between the Swift and Objective-C code; a separate framework already exposes its public Objective-C API to Swift through its own module map, consumed with an ordinary import.
Why can't a bridging header be used to expose a separate framework's Objective-C API to Swift? A. Because frameworks cannot contain Objective-C code at all B. Because a bridging header's effect is scoped to its one target and does not cross a module/framework boundary — that boundary is handled by the framework's own module map instead C. Because Swift refuses to compile any bridging header referencing a framework D. Because bridging headers can only contain a single #import statement
B. Because a bridging header's effect is scoped to its one target and does not cross a module/framework boundary — that boundary is handled by the framework's own module map instead — A bridging header only applies within the single target it is configured for; it has no mechanism for reaching across an actual framework/module boundary, which is instead handled by that framework's own module map.
Why does a very large bridging header measurably slow down build times, beyond just being hard to read? A. Because Swift must reprocess the ENTIRE bridging header on relevant rebuilds, so a huge one means far more gets reprocessed than any single Swift file actually needs B. Because Objective-C code always compiles slower than Swift code regardless of bridging headers C. Because Xcode limits bridging headers to 100 lines and rejects anything larger D. Because a bridging header must be manually recompiled by the developer, not the build system
A. Because Swift must reprocess the ENTIRE bridging header on relevant rebuilds, so a huge one means far more gets reprocessed than any single Swift file actually needs — Swift reprocesses the whole bridging header on rebuilds where it (or something it imports) is considered changed; an oversized bridging header spanning a lot of unrelated legacy code means a much larger surface area gets reprocessed than any given Swift file's actual, narrow dependency would require.
What is the recommended long-term pattern for a large or growing body of Objective-C code, instead of relying on one large shared bridging header? A. Split it across several smaller bridging headers, one per feature area B. Wrap it in its own proper framework with a module map, so it's consumed via import rather than growing the shared bridging header C. Rewrite the entire codebase in Swift immediately, since bridging headers cannot scale at all D. Disable incremental builds so bridging header size no longer matters
B. Wrap it in its own proper framework with a module map, so it's consumed via import rather than growing the shared bridging header — The better long-term pattern for larger or cross-target Objective-C code is extracting it into its own framework with a proper module map, exposed via an ordinary import, keeping any remaining bridging header narrowly scoped to what genuinely still needs it.
A Swift-based app target has a bridging header importing: (1) EHLegacyAuthManager.h, a same-target Objective-C class still used by two Swift view controllers; (2) the umbrella header of EHAnalyticsSDK, a separately-distributed CocoaPods framework; and (3) EHOldExportTool.h, a same-target Objective-C class no Swift code calls anymore. For each of the three imports, say whether it belongs in the bridging header at all, and if so, whether it should stay as-is or be changed.
(1) EHLegacyAuthManager.h belongs in the bridging header and should stay — it's same-target Objective-C code genuinely still used by Swift. (2) The EHAnalyticsSDK umbrella import does NOT belong in the bridging header at all — it's a separately distributed framework with its own module map, and should instead be consumed via import EHAnalyticsSDK in the Swift files that need it, with the bridging-header line removed. (3) EHOldExportTool.h should be removed from the bridging header as stale cleanup, since no Swift code calls it anymore — leaving it costs rebuild time for no benefit.
For each import, ask: is this code in the same target with no module boundary, or is it a separately distributed framework with its own module map? And separately: is it actually still used by any Swift code?